From 359985e7401d2e8c68e54feba6818892c631afda Mon Sep 17 00:00:00 2001 From: Mikhail Revzin Date: Wed, 17 Jan 2024 23:11:24 +0300 Subject: [PATCH] TalkPE calls API (#617) --- TESTING.md | 3 +- specification/talk/call_pe.json | 51 +++ specification/talk/voice_comment.json | 12 + .../betamax/TestCallPE.test_add_comment.json | 247 ++++++++++++ .../TestCallPE.test_create_update_get.json | 367 ++++++++++++++++++ .../TestCallPE.test_create_with_comment-.json | 125 ++++++ tests/test_api/test_calls_pe.py | 87 +++++ zenpy/__init__.py | 2 + zenpy/lib/api.py | 44 ++- zenpy/lib/api_objects/talk_objects.py | 265 +++++++++++++ zenpy/lib/endpoint.py | 5 + zenpy/lib/mapping.py | 15 +- zenpy/lib/response.py | 17 + 13 files changed, 1236 insertions(+), 4 deletions(-) create mode 100644 specification/talk/call_pe.json create mode 100644 specification/talk/voice_comment.json create mode 100644 tests/test_api/betamax/TestCallPE.test_add_comment.json create mode 100644 tests/test_api/betamax/TestCallPE.test_create_update_get.json create mode 100644 tests/test_api/betamax/TestCallPE.test_create_with_comment-.json create mode 100644 tests/test_api/test_calls_pe.py diff --git a/TESTING.md b/TESTING.md index deff668f..9390f79d 100644 --- a/TESTING.md +++ b/TESTING.md @@ -57,8 +57,9 @@ To pass all the tests successfully, please provide some things: 1. In **./zenpy/cache.py:ZenpyCacheManager.__init__** temporary change ttl for ticket cache to 300. Some tests take too much time and fail on expired cache elements. 2. In **./zenpy/tests/test_api/test_incremental_object_update.py:TestIncrementalObjectUpdate.setUp** provide an existing ticket id. 3. In **./zenpy/tests/test_api/test_webhooks_api.py:TestWebhooks.test_invocations** and **.test_invocation_attempts** put a real webhook id with a non-empty list of invocations. +4. in **./zenpy/tests/test_api/test_calls_pe.py** put a real TEST_APP_ID for TalkPE Calls API. (You can just call "https://{{subdomain}}.zendesk.com/api/v2/apps/installations" and choose an ID you like the most). 5. In the scripts folder, you can remove all betamax outputs with ```clean_betamax.sh``` -7. Look for all TESTING_CHANGE in the source and REVERT ones that say it's NOT OK TO COMMIT. +6. Look for all TESTING_CHANGE in the source and REVERT ones that say it's NOT OK TO COMMIT. Notes: diff --git a/specification/talk/call_pe.json b/specification/talk/call_pe.json new file mode 100644 index 00000000..a41ed74f --- /dev/null +++ b/specification/talk/call_pe.json @@ -0,0 +1,51 @@ +{ + "agent_id": 88902883, + "app_id": 735264019863524, + "brand_id": 9157783614, + "call_disposition": "Call again later", + "call_ended_at": "2022-01-27T15:31:40-04:00", + "call_recording_consent": "opt_in", + "call_recording_consent_action": "caller_opted_out", + "call_started_at": "2022-01-27T15:31:40+01", + "call_type": "PSTN", + "callback_number": "+1 (661) 748-0123", + "callback_source": "queue", + "completion_status": "completed", + "consultation_time": 67, + "customer_requested_voicemail": false, + "direction": "inbound", + "dnis": "0800-12345", + "duration": 345, + "end_user_id": 7827, + "end_user_location": "Dublin", + "exceeded_queue_time": true, + "external_id": "1245HN567", + "from_line": "+183808333456", + "from_line_nickname": "Sales", + "hold_time": 120, + "id": 1, + "ivr_action": "voicemail", + "ivr_destination_group_name": "Billing", + "ivr_hops": 3, + "ivr_routed_to": "+1311123456789", + "ivr_time_spent": 21, + "not_recording_time": 210, + "outside_business_hours": false, + "overflowed": false, + "overflowed_to": "+1 (661) 748-0123", + "phone_name": "1300 SELL", + "queue_name": "priority_one", + "queue_time": 36, + "recording_control_interactions": 3, + "recording_time": 210, + "recording_url": "https://somedomain.com/recording/123.mp3", + "talk_time": 360, + "ticket_id": 37987922, + "time_to_answer": 24, + "to_line": "+149488484873", + "to_line_nickname": "Technical Support", + "transcript": "There is an issue with my printer. Can you help? ...", + "voicemail": false, + "wait_time": 20, + "wrap_up_time": 210 +} \ No newline at end of file diff --git a/specification/talk/voice_comment.json b/specification/talk/voice_comment.json new file mode 100644 index 00000000..d0cd6af1 --- /dev/null +++ b/specification/talk/voice_comment.json @@ -0,0 +1,12 @@ +{ + "call_fields": [ + "from_line", + "to_line", + "call_started_at" + ], + "title": "This is the ticket comment", + "subject": "This is the ticket comment", + "author_id": 1, + "end_user_id": 2, + "display_to_agent": 3 +} \ No newline at end of file diff --git a/tests/test_api/betamax/TestCallPE.test_add_comment.json b/tests/test_api/betamax/TestCallPE.test_add_comment.json new file mode 100644 index 00000000..f6459669 --- /dev/null +++ b/tests/test_api/betamax/TestCallPE.test_add_comment.json @@ -0,0 +1,247 @@ +{ + "http_interactions": [ + { + "recorded_at": "2024-01-17T18:31:18", + "request": { + "body": { + "encoding": "utf-8", + "string": "{\"call\": {\"app_id\": 1003048, \"call_ended_at\": \"2022-01-27T15:32:40+01\", \"call_started_at\": \"2022-01-27T15:31:40+01\", \"direction\": \"inbound\", \"from_line\": \"+183808333456\", \"from_line_nickname\": \"Sales\", \"to_line\": \"+149488484873\", \"to_line_nickname\": \"Technical Support\"}}" + }, + "headers": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic " + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "271" + ], + "Content-Type": [ + "application/json" + ], + "User-Agent": [ + "Zenpy/2.0.45" + ] + }, + "method": "POST", + "uri": "https://d3v-zenpydev.zendesk.com/api/v2/calls.json" + }, + "response": { + "body": { + "encoding": "utf-8", + "string": "{\"call\":{\"id\":16444799095826,\"account_id\":850916,\"ticket_id\":null,\"end_user_id\":null,\"organization_id\":null,\"brand_id\":null,\"app_id\":1003048,\"agent_id\":null,\"from_line\":\"+183808333456\",\"from_line_nickname\":\"Sales\",\"to_line\":\"+149488484873\",\"to_line_nickname\":\"Technical Support\",\"call_started_at\":\"2022-01-27T14:31:40Z\",\"call_ended_at\":\"2022-01-27T14:32:40Z\",\"direction\":\"inbound\",\"duration\":null,\"dnis\":null,\"phone_name\":null,\"call_type\":null,\"end_user_location\":null,\"callback\":null,\"callback_source\":null,\"callback_number\":null,\"wait_time\":null,\"time_to_answer\":null,\"queue_time\":null,\"exceeded_queue_time\":null,\"queue_name\":null,\"ivr_time_spent\":null,\"ivr_hops\":null,\"ivr_action\":null,\"ivr_routed_to\":null,\"ivr_destination_group_name\":null,\"talk_time\":null,\"hold_time\":null,\"consultation_time\":null,\"wrap_up_time\":null,\"completion_status\":null,\"call_abandoned_position\":null,\"customer_requested_voicemail\":null,\"voicemail\":null,\"outside_business_hours\":null,\"overflowed\":null,\"overflowed_to\":null,\"call_disposition\":null,\"recording_url\":null,\"recording_time\":null,\"not_recording_time\":null,\"call_recording_consent_action\":null,\"recording_control_interactions\":null,\"created_at\":\"2024-01-17T18:31:18Z\",\"updated_at\":\"2024-01-17T18:31:18Z\",\"transcript\":null,\"external_id\":null}}" + }, + "headers": { + "CF-Cache-Status": [ + "DYNAMIC" + ], + "CF-RAY": [ + "8470aca30c8b5c3e-FRA" + ], + "Connection": [ + "keep-alive" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Wed, 17 Jan 2024 18:31:18 GMT" + ], + "NEL": [ + "{\"success_fraction\":0.01,\"report_to\":\"cf-nel\",\"max_age\":604800}" + ], + "Report-To": [ + "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=IqYGSSYdXopv3puOt%2FQgbimck%2B7bEiGutNrQnp%2FXjVcmnF%2FnAR8RCL8Ue%2B7FLY%2B3AzlqO7VBgDW8dfCFwiREM3iDfiXwrl1wnhbkl1C5LEReZ1gvGoFOGHSq%2BpoIA69cPS9k2mZNTA%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}" + ], + "Server": [ + "cloudflare" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Zendesk-Zorg": [ + "yes" + ], + "cache-control": [ + "max-age=0, private, must-revalidate" + ], + "content-security-policy-report-only": [ + "base-uri 'self'; script-src 'self' https: *.zende.sk *.zendesk.com 'unsafe-eval' 'report-sample' *.googletagmanager.com tagmanager.google.com *.zdassets.com static.zdassets.com 'nonce-ELSGWSpM3JQKv7NgqM4U1A=='; img-src 'self' https: *.zende.sk *.zendesk.com data: blob: *.zdusercontent.com *.apps.zdusercontent.com *.google-analytics.com *.googletagmanager.com ssl.gstatic.com *.gstatic.com *.zdassets.com *.g.doubleclick.net; font-src 'self' https: *.zende.sk *.zendesk.com data: fonts.gstatic.com *.typekit.net *.googleusercontent.com cdn.jsdelivr.net; media-src 'self' https: *.zende.sk *.zendesk.com blob: data:; worker-src 'self' https: *.zende.sk *.zendesk.com blob:; connect-src 'self' https: *.zende.sk *.zendesk.com ws: wss: api.segment.io; child-src 'self' https: *.zende.sk *.zendesk.com; object-src 'self' https: *.zende.sk *.zendesk.com; report-uri https://zendesk-eu.my.sentry.io/api/299/security/?sentry_key=283035aa3c1947e2ba44514578764430&sentry_environment=production" + ], + "etag": [ + "W/\"80e7d694da9e6a4abc494f016a2eec87\"" + ], + "set-cookie": [ + "_voice_session=SjljNzNnUkh3R0VTNHQwYXc4Z2FjQUkrdnZRbU5YUEJCODJSOEFwY0YyVlZIdmU4NTM5blRKakFtclNOYWFqR3ZObVNTTlp6TktXdTZPd2w3bDFXb3VzL25TYUNPM3BXcDJtWVNJQVRPdlppTnFoOW1oZ2lYdEFZWm1NQlIxcTM1TG4wNHo0UkFEN2tkMVpIbEVNaVZ3PT0tLTdSYTd2dTRlVE5saUhQbHVBNVU2OFE9PQ%3D%3D--efa43ee74ce8b2efc89ce01c4afe69dacdc567bb; path=/; secure; HttpOnly", + "__cfruid=0944e55a485c8c5f24050fecca77e3fa756ae200-1705516278; path=/; domain=.d3v-zenpydev.zendesk.com; HttpOnly; Secure; SameSite=None", + "_cfuvid=ntaBUGPVn3_BNpVH.hqGqLPVQ5tge0gJoHDurVlT0vA-1705516278362-0-604800000; path=/; domain=.d3v-zenpydev.zendesk.com; HttpOnly; Secure; SameSite=None" + ], + "strict-transport-security": [ + "max-age=31536000; includeSubDomains" + ], + "x-frame-options": [ + "SAMEORIGIN" + ], + "x-rate-limit": [ + "700" + ], + "x-rate-limit-remaining": [ + "700" + ], + "x-request-id": [ + "8470aca30c8b5c3e-FRA", + "8470aca30c8b5c3e-FRA" + ], + "x-runtime": [ + "0.070968" + ], + "x-xss-protection": [ + "1; mode=block" + ], + "x-zendesk-api-version": [ + "v2" + ], + "x-zendesk-origin-server": [ + "voice-app-server-59c955f4bd-l6wh9" + ] + }, + "status": { + "code": 201, + "message": "Created" + }, + "url": "https://d3v-zenpydev.zendesk.com/api/v2/calls.json" + } + }, + { + "recorded_at": "2024-01-17T18:31:18", + "request": { + "body": { + "encoding": "utf-8", + "string": "{\"call_fields\": [\"direction\"], \"title\": \"This is a ticket comment 2\"}" + }, + "headers": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic " + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "69" + ], + "Content-Type": [ + "application/json" + ], + "Cookie": [ + "__cfruid=0944e55a485c8c5f24050fecca77e3fa756ae200-1705516278; _cfuvid=ntaBUGPVn3_BNpVH.hqGqLPVQ5tge0gJoHDurVlT0vA-1705516278362-0-604800000; _voice_session=SjljNzNnUkh3R0VTNHQwYXc4Z2FjQUkrdnZRbU5YUEJCODJSOEFwY0YyVlZIdmU4NTM5blRKakFtclNOYWFqR3ZObVNTTlp6TktXdTZPd2w3bDFXb3VzL25TYUNPM3BXcDJtWVNJQVRPdlppTnFoOW1oZ2lYdEFZWm1NQlIxcTM1TG4wNHo0UkFEN2tkMVpIbEVNaVZ3PT0tLTdSYTd2dTRlVE5saUhQbHVBNVU2OFE9PQ%3D%3D--efa43ee74ce8b2efc89ce01c4afe69dacdc567bb" + ], + "User-Agent": [ + "Zenpy/2.0.45" + ] + }, + "method": "POST", + "uri": "https://d3v-zenpydev.zendesk.com/api/v2/calls/16444799095826/comments.json" + }, + "response": { + "body": { + "encoding": "utf-8", + "string": "{\"id\":16444825011986,\"type\":\"TpeVoiceComment\",\"public\":false,\"data\":{\"app_id\":1003048,\"app_name\":\"3CX calls\",\"author_id\":null,\"call_id\":16444799095826,\"direction\":\"inbound\",\"external_id\":null,\"title\":\"This is a ticket comment 2\",\"via_id\":45,\"recording_url\":\"\"},\"formatted_from\":\"Unknown\",\"formatted_to\":\"Unknown\",\"transcription_visible\":false,\"author_id\":1568991111,\"body\":\"This is a ticket comment 2\\n\\nSummary\\nDirection: inbound\",\"html_body\":\"\\u003cdiv class=\\\"zd-comment\\\" dir=\\\"auto\\\"\\u003e\\u003cp dir=\\\"auto\\\"\\u003eThis is a ticket comment 2\\u003c/p\\u003e\\n\\u003cp dir=\\\"auto\\\"\\u003eSummary\\u003cbr\\u003e\\nDirection: inbound\\u003c/p\\u003e\\u003c/div\\u003e\",\"trusted\":true,\"attachments\":[],\"created_at\":\"2024-01-17T18:31:18Z\",\"audit_id\":16444825011858,\"title\":\"This is a ticket comment 2\"}" + }, + "headers": { + "CF-Cache-Status": [ + "DYNAMIC" + ], + "CF-RAY": [ + "8470aca44dd25c3e-FRA" + ], + "Connection": [ + "keep-alive" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Wed, 17 Jan 2024 18:31:19 GMT" + ], + "NEL": [ + "{\"success_fraction\":0.01,\"report_to\":\"cf-nel\",\"max_age\":604800}" + ], + "Report-To": [ + "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=uTPRTFSAo4rfBuu5SsMN9kxtSpFrKB2PcguIwg6Whxfm%2Bgeh2BJpSP0bTZ2A3WxTJxhD%2BCRA9zsTAtcXUwGxWW3N7d1K1Ej9Cpv3JLDf%2F%2B9BB04bEzVGztzv4%2FY%2FcHP3Zo9NESib5w%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}" + ], + "Server": [ + "cloudflare" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Zendesk-Zorg": [ + "yes" + ], + "cache-control": [ + "max-age=0, private, must-revalidate" + ], + "content-security-policy-report-only": [ + "base-uri 'self'; script-src 'self' https: *.zende.sk *.zendesk.com 'unsafe-eval' 'report-sample' *.googletagmanager.com tagmanager.google.com *.zdassets.com static.zdassets.com 'nonce-j59zZL7gYcMecVhfM1coRw=='; img-src 'self' https: *.zende.sk *.zendesk.com data: blob: *.zdusercontent.com *.apps.zdusercontent.com *.google-analytics.com *.googletagmanager.com ssl.gstatic.com *.gstatic.com *.zdassets.com *.g.doubleclick.net; font-src 'self' https: *.zende.sk *.zendesk.com data: fonts.gstatic.com *.typekit.net *.googleusercontent.com cdn.jsdelivr.net; media-src 'self' https: *.zende.sk *.zendesk.com blob: data:; worker-src 'self' https: *.zende.sk *.zendesk.com blob:; connect-src 'self' https: *.zende.sk *.zendesk.com ws: wss: api.segment.io; child-src 'self' https: *.zende.sk *.zendesk.com; object-src 'self' https: *.zende.sk *.zendesk.com; report-uri https://zendesk-eu.my.sentry.io/api/299/security/?sentry_key=283035aa3c1947e2ba44514578764430&sentry_environment=production" + ], + "etag": [ + "W/\"ce9ca2d1c7a80b059a26ffe06af87044\"" + ], + "set-cookie": [ + "_voice_session=SEhqN1NQVnF3YmZCRko1aGpML0Jxa0dsWE5iSll5UTcwRTJrOU9uejBOOEtjTTlnaW91QkZXbWdjMkRCR2p1MUJtZmdGQ0l5blp0alYzbzhFZ2RpZStZV24xcnBXK3FCTEw0MzUwclVqZEQwZzV5RHV5SUJ1MFVUZ0lDMTVhR1BpQ0d2Slh5aElNTWVXUVgzc3psek9BPT0tLWhrRjJINkU5SjR6b0o3QTltZldkeVE9PQ%3D%3D--ef9629243828e49fcae4cef453fb10f1d7a04932; path=/; secure; HttpOnly" + ], + "strict-transport-security": [ + "max-age=31536000; includeSubDomains" + ], + "x-frame-options": [ + "SAMEORIGIN" + ], + "x-rate-limit": [ + "700" + ], + "x-rate-limit-remaining": [ + "700" + ], + "x-request-id": [ + "8470aca44dd25c3e-FRA", + "8470aca44dd25c3e-FRA" + ], + "x-runtime": [ + "0.533681" + ], + "x-xss-protection": [ + "1; mode=block" + ], + "x-zendesk-api-version": [ + "v2" + ], + "x-zendesk-api-warn": [ + "Removed restricted keys [\"comment\"] from parameters according to whitelist" + ], + "x-zendesk-origin-server": [ + "voice-app-server-59c955f4bd-nb99k" + ] + }, + "status": { + "code": 201, + "message": "Created" + }, + "url": "https://d3v-zenpydev.zendesk.com/api/v2/calls/16444799095826/comments.json" + } + } + ], + "recorded_with": "betamax/0.8.1" +} \ No newline at end of file diff --git a/tests/test_api/betamax/TestCallPE.test_create_update_get.json b/tests/test_api/betamax/TestCallPE.test_create_update_get.json new file mode 100644 index 00000000..4fbd8340 --- /dev/null +++ b/tests/test_api/betamax/TestCallPE.test_create_update_get.json @@ -0,0 +1,367 @@ +{ + "http_interactions": [ + { + "recorded_at": "2024-01-17T18:31:19", + "request": { + "body": { + "encoding": "utf-8", + "string": "{\"call\": {\"app_id\": 1003048, \"call_ended_at\": \"2022-01-27T15:32:40+01\", \"call_started_at\": \"2022-01-27T15:31:40+01\", \"direction\": \"inbound\", \"from_line\": \"+183808333456\", \"from_line_nickname\": \"Sales\", \"to_line\": \"+149488484873\", \"to_line_nickname\": \"Technical Support\"}}" + }, + "headers": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic " + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "271" + ], + "Content-Type": [ + "application/json" + ], + "Cookie": [ + "__cfruid=0944e55a485c8c5f24050fecca77e3fa756ae200-1705516278; _cfuvid=ntaBUGPVn3_BNpVH.hqGqLPVQ5tge0gJoHDurVlT0vA-1705516278362-0-604800000; _voice_session=SEhqN1NQVnF3YmZCRko1aGpML0Jxa0dsWE5iSll5UTcwRTJrOU9uejBOOEtjTTlnaW91QkZXbWdjMkRCR2p1MUJtZmdGQ0l5blp0alYzbzhFZ2RpZStZV24xcnBXK3FCTEw0MzUwclVqZEQwZzV5RHV5SUJ1MFVUZ0lDMTVhR1BpQ0d2Slh5aElNTWVXUVgzc3psek9BPT0tLWhrRjJINkU5SjR6b0o3QTltZldkeVE9PQ%3D%3D--ef9629243828e49fcae4cef453fb10f1d7a04932" + ], + "User-Agent": [ + "Zenpy/2.0.45" + ] + }, + "method": "POST", + "uri": "https://d3v-zenpydev.zendesk.com/api/v2/calls.json" + }, + "response": { + "body": { + "encoding": "utf-8", + "string": "{\"call\":{\"id\":16444799132306,\"account_id\":850916,\"ticket_id\":null,\"end_user_id\":null,\"organization_id\":null,\"brand_id\":null,\"app_id\":1003048,\"agent_id\":null,\"from_line\":\"+183808333456\",\"from_line_nickname\":\"Sales\",\"to_line\":\"+149488484873\",\"to_line_nickname\":\"Technical Support\",\"call_started_at\":\"2022-01-27T14:31:40Z\",\"call_ended_at\":\"2022-01-27T14:32:40Z\",\"direction\":\"inbound\",\"duration\":null,\"dnis\":null,\"phone_name\":null,\"call_type\":null,\"end_user_location\":null,\"callback\":null,\"callback_source\":null,\"callback_number\":null,\"wait_time\":null,\"time_to_answer\":null,\"queue_time\":null,\"exceeded_queue_time\":null,\"queue_name\":null,\"ivr_time_spent\":null,\"ivr_hops\":null,\"ivr_action\":null,\"ivr_routed_to\":null,\"ivr_destination_group_name\":null,\"talk_time\":null,\"hold_time\":null,\"consultation_time\":null,\"wrap_up_time\":null,\"completion_status\":null,\"call_abandoned_position\":null,\"customer_requested_voicemail\":null,\"voicemail\":null,\"outside_business_hours\":null,\"overflowed\":null,\"overflowed_to\":null,\"call_disposition\":null,\"recording_url\":null,\"recording_time\":null,\"not_recording_time\":null,\"call_recording_consent_action\":null,\"recording_control_interactions\":null,\"created_at\":\"2024-01-17T18:31:19Z\",\"updated_at\":\"2024-01-17T18:31:19Z\",\"transcript\":null,\"external_id\":null}}" + }, + "headers": { + "CF-Cache-Status": [ + "DYNAMIC" + ], + "CF-RAY": [ + "8470aca86ac25c3e-FRA" + ], + "Connection": [ + "keep-alive" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Wed, 17 Jan 2024 18:31:19 GMT" + ], + "NEL": [ + "{\"success_fraction\":0.01,\"report_to\":\"cf-nel\",\"max_age\":604800}" + ], + "Report-To": [ + "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=%2BzwqApJicogRP7cB1ap8m8FjzWCFumkpPwR3D8JSkIisvcSvtZgIpglnVWb%2FJsMoRM4%2FenULE9haiIj5dhJgkPfW4uoBJgEAksfVehUKLneQgd8JI1b%2FBlB69ohRZi2pxKkRfDStVw%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}" + ], + "Server": [ + "cloudflare" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Zendesk-Zorg": [ + "yes" + ], + "cache-control": [ + "max-age=0, private, must-revalidate" + ], + "content-security-policy-report-only": [ + "base-uri 'self'; script-src 'self' https: *.zende.sk *.zendesk.com 'unsafe-eval' 'report-sample' *.googletagmanager.com tagmanager.google.com *.zdassets.com static.zdassets.com 'nonce-YY/o4OE1L+pC0ZG8U2fgaw=='; img-src 'self' https: *.zende.sk *.zendesk.com data: blob: *.zdusercontent.com *.apps.zdusercontent.com *.google-analytics.com *.googletagmanager.com ssl.gstatic.com *.gstatic.com *.zdassets.com *.g.doubleclick.net; font-src 'self' https: *.zende.sk *.zendesk.com data: fonts.gstatic.com *.typekit.net *.googleusercontent.com cdn.jsdelivr.net; media-src 'self' https: *.zende.sk *.zendesk.com blob: data:; worker-src 'self' https: *.zende.sk *.zendesk.com blob:; connect-src 'self' https: *.zende.sk *.zendesk.com ws: wss: api.segment.io; child-src 'self' https: *.zende.sk *.zendesk.com; object-src 'self' https: *.zende.sk *.zendesk.com; report-uri https://zendesk-eu.my.sentry.io/api/299/security/?sentry_key=283035aa3c1947e2ba44514578764430&sentry_environment=production" + ], + "etag": [ + "W/\"0911bf3e8cabdf12dcd99e1a38c5bf6f\"" + ], + "set-cookie": [ + "_voice_session=ZFlueUZTdmpVQkNHb2N5a211d3QxN3I2VHNtV0FBOUVPNVRzYWl2a1pNeG94NkVaNmY1eVNDV21SL2pEYlNxNFVxTmxGZmdKVkQzWGVaQ0VQanBGQ3VHL3pTc21kQTBrTVluRjBrTy85MDdkQVdvb2s5QXZrci9zdUt3SmtDV2c2a3FuNVJjUmtMZk1HWHh1cVdUL09BPT0tLTdvRzJFQUdqKy9mVEhsOXdEb2prZ2c9PQ%3D%3D--3bdabe4eb7a8ded09aa646d6c2a83b999a2ba588; path=/; secure; HttpOnly" + ], + "strict-transport-security": [ + "max-age=31536000; includeSubDomains" + ], + "x-frame-options": [ + "SAMEORIGIN" + ], + "x-rate-limit": [ + "700" + ], + "x-rate-limit-remaining": [ + "700" + ], + "x-request-id": [ + "8470aca86ac25c3e-FRA", + "8470aca86ac25c3e-FRA" + ], + "x-runtime": [ + "0.091293" + ], + "x-xss-protection": [ + "1; mode=block" + ], + "x-zendesk-api-version": [ + "v2" + ], + "x-zendesk-origin-server": [ + "voice-app-server-59c955f4bd-lzp27" + ] + }, + "status": { + "code": 201, + "message": "Created" + }, + "url": "https://d3v-zenpydev.zendesk.com/api/v2/calls.json" + } + }, + { + "recorded_at": "2024-01-17T18:31:19", + "request": { + "body": { + "encoding": "utf-8", + "string": "{\"call\": {\"call_ended_at\": \"2022-04-16T09:15:37Z\", \"id\": 16444799132306}}" + }, + "headers": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic " + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "73" + ], + "Content-Type": [ + "application/json" + ], + "Cookie": [ + "__cfruid=0944e55a485c8c5f24050fecca77e3fa756ae200-1705516278; _cfuvid=ntaBUGPVn3_BNpVH.hqGqLPVQ5tge0gJoHDurVlT0vA-1705516278362-0-604800000; _voice_session=ZFlueUZTdmpVQkNHb2N5a211d3QxN3I2VHNtV0FBOUVPNVRzYWl2a1pNeG94NkVaNmY1eVNDV21SL2pEYlNxNFVxTmxGZmdKVkQzWGVaQ0VQanBGQ3VHL3pTc21kQTBrTVluRjBrTy85MDdkQVdvb2s5QXZrci9zdUt3SmtDV2c2a3FuNVJjUmtMZk1HWHh1cVdUL09BPT0tLTdvRzJFQUdqKy9mVEhsOXdEb2prZ2c9PQ%3D%3D--3bdabe4eb7a8ded09aa646d6c2a83b999a2ba588" + ], + "User-Agent": [ + "Zenpy/2.0.45" + ] + }, + "method": "PATCH", + "uri": "https://d3v-zenpydev.zendesk.com/api/v2/calls/16444799132306" + }, + "response": { + "body": { + "base64_string": "H4sIAAAAAAAAA4SUzW7bMAzHX2XQdSlgx27i+DWa0y6CInGJEIVUKarZVvTdB9lIIzcDdjN//Ev8Ev2urAlBje/KOzW2m77vt7td2627ZrNSxlrKKLr4hudm125WSrw9w4wwh7BSgE7nBFwh4qNB/8eIJ6zwgQ26yjYxTlbbNF3TDytljoD11T+ZLjp4BDWq7+3QDc3QdV3/vFGVT6O3ZzSXInoxAZJaKaH7sX7XD0M/9MO2u3vqQ3uwJ/TWhG8vOUZiUaupKzqJYQGnjahRrZv1+qlpn9bbfduPXTv2zY+bENAtZf1Tu9k3u7F9HrttkTnPYEs71Kg8HiijKzSzmeFcr0Ofbt/xRCXNKcWZTKHkd4SHzgeyi3uK8mDs+autE2W28IAxXw7AN3w1XrT4e+DyrYW0wXS9y14zZFjo4JcFKJ14dM2krsa/8aTQKQJKTU8UU20bW9dWCFMucxGqoYMkHuc3d2TKcRFOTDgvMjpRcAtgCVMOMl9QO65sos7xi/gSA0zSJEZyWgzJHAw6QnA6UvKLyeQkdAHWDK8ZUqnijbyFi/HhpnkAlCV5B/qQk0dISZ8o82dEegP+GegK7pFULZoScz59zYjBEjuPR505PMK6aCTR//ZMl99dpZVlk5eTW/iFKWiPAjyL7g1kMPXS9WXp2u2+HcrStbuyTTm6/2qEDSbLPsr9eQowmvD5g/n4+AsAAP//AwB2C7Rx/wQAAA==", + "encoding": "utf-8", + "string": "" + }, + "headers": { + "CF-Cache-Status": [ + "DYNAMIC" + ], + "CF-RAY": [ + "8470acaa2ca85c3e-FRA" + ], + "Connection": [ + "keep-alive" + ], + "Content-Encoding": [ + "gzip" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Wed, 17 Jan 2024 18:31:19 GMT" + ], + "NEL": [ + "{\"success_fraction\":0.01,\"report_to\":\"cf-nel\",\"max_age\":604800}" + ], + "Report-To": [ + "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=wXwDOVurcmxkZ31NKy%2F33M6MG%2B0TnfClN9EZ6gnPBjDkR3TkyIufVWKQmO77q9o082BYDWCcmJKWhwil%2BZtKo8H%2FvSLimdGODVSycmGes7nA%2BSFeobFcsGJbY88knswHJinuIKXBpg%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}" + ], + "Server": [ + "cloudflare" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Zendesk-Zorg": [ + "yes" + ], + "cache-control": [ + "max-age=0, private, must-revalidate" + ], + "content-security-policy-report-only": [ + "base-uri 'self'; script-src 'self' https: *.zende.sk *.zendesk.com 'unsafe-eval' 'report-sample' *.googletagmanager.com tagmanager.google.com *.zdassets.com static.zdassets.com 'nonce-VTLsKVTm9S+EIhd+ks7J2g=='; img-src 'self' https: *.zende.sk *.zendesk.com data: blob: *.zdusercontent.com *.apps.zdusercontent.com *.google-analytics.com *.googletagmanager.com ssl.gstatic.com *.gstatic.com *.zdassets.com *.g.doubleclick.net; font-src 'self' https: *.zende.sk *.zendesk.com data: fonts.gstatic.com *.typekit.net *.googleusercontent.com cdn.jsdelivr.net; media-src 'self' https: *.zende.sk *.zendesk.com blob: data:; worker-src 'self' https: *.zende.sk *.zendesk.com blob:; connect-src 'self' https: *.zende.sk *.zendesk.com ws: wss: api.segment.io; child-src 'self' https: *.zende.sk *.zendesk.com; object-src 'self' https: *.zende.sk *.zendesk.com; report-uri https://zendesk-eu.my.sentry.io/api/299/security/?sentry_key=283035aa3c1947e2ba44514578764430&sentry_environment=production" + ], + "etag": [ + "W/\"c27dbec3647d598be6f4d0f642e151b5\"" + ], + "set-cookie": [ + "_voice_session=ZjBVL3l3eEpNZVE0Q2h0ZzBNN0ltNGR2Q284L09oVk14bm5wemVPQUYrNTc5QWkrYzBHYVhyUGtrRlJjK2k4aUdEV3BYOG4vR0ZweXVHZGZ2c3lsSGFaSGFpRE8rS1YvQjlBN1hISGVmZVFOSE54aFBWTjNTYlpPUlhXb0YrQWdKS3ZVTitGWVExTjcveUxDTlI5TDh3PT0tLVJMSDVicE1FcERYUGp6WU5UaHFIZHc9PQ%3D%3D--b91f2750317c8b9409667ce0b8347f95639c5ccc; path=/; secure; HttpOnly" + ], + "strict-transport-security": [ + "max-age=31536000; includeSubDomains" + ], + "x-frame-options": [ + "SAMEORIGIN" + ], + "x-rate-limit": [ + "700" + ], + "x-rate-limit-remaining": [ + "700" + ], + "x-request-id": [ + "8470acaa2ca85c3e-FRA", + "8470acaa2ca85c3e-FRA" + ], + "x-runtime": [ + "0.094953" + ], + "x-xss-protection": [ + "1; mode=block" + ], + "x-zendesk-api-version": [ + "v2" + ], + "x-zendesk-origin-server": [ + "voice-app-server-59c955f4bd-cff2w" + ] + }, + "status": { + "code": 200, + "message": "OK" + }, + "url": "https://d3v-zenpydev.zendesk.com/api/v2/calls/16444799132306" + } + }, + { + "recorded_at": "2024-01-17T18:31:19", + "request": { + "body": { + "encoding": "utf-8", + "string": "" + }, + "headers": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic " + ], + "Connection": [ + "keep-alive" + ], + "Cookie": [ + "__cfruid=0944e55a485c8c5f24050fecca77e3fa756ae200-1705516278; _cfuvid=ntaBUGPVn3_BNpVH.hqGqLPVQ5tge0gJoHDurVlT0vA-1705516278362-0-604800000; _voice_session=ZjBVL3l3eEpNZVE0Q2h0ZzBNN0ltNGR2Q284L09oVk14bm5wemVPQUYrNTc5QWkrYzBHYVhyUGtrRlJjK2k4aUdEV3BYOG4vR0ZweXVHZGZ2c3lsSGFaSGFpRE8rS1YvQjlBN1hISGVmZVFOSE54aFBWTjNTYlpPUlhXb0YrQWdKS3ZVTitGWVExTjcveUxDTlI5TDh3PT0tLVJMSDVicE1FcERYUGp6WU5UaHFIZHc9PQ%3D%3D--b91f2750317c8b9409667ce0b8347f95639c5ccc" + ], + "User-Agent": [ + "Zenpy/2.0.45" + ] + }, + "method": "GET", + "uri": "https://d3v-zenpydev.zendesk.com/api/v2/calls/16444799132306" + }, + "response": { + "body": { + "base64_string": "H4sIAAAAAAAAA4SUzW7bMAzHX2XQdSlgx27i+DWa0y6CInGJEIVUKarZVvTdB9lIIzcDdjN//Ev8Ev2urAlBje/KOzW2m77vt7td2627ZrNSxlrKKLr4hudm125WSrw9w4wwh7BSgE7nBFwh4qNB/8eIJ6zwgQ26yjYxTlbbNF3TDytljoD11T+ZLjp4BDWq7+3QDc3QdV3/vFGVT6O3ZzSXInoxAZJaKaH7sX7XD0M/9MO2u3vqQ3uwJ/TWhG8vOUZiUaupKzqJYQGnjahRrZv1+qlpn9bbfduPXTv2zY+bENAtZf1Tu9k3u7F9HrttkTnPYEs71Kg8HiijKzSzmeFcr0Ofbt/xRCXNKcWZTKHkd4SHzgeyi3uK8mDs+autE2W28IAxXw7AN3w1XrT4e+DyrYW0wXS9y14zZFjo4JcFKJ14dM2krsa/8aTQKQJKTU8UU20bW9dWCFMucxGqoYMkHuc3d2TKcRFOTDgvMjpRcAtgCVMOMl9QO65sos7xi/gSA0zSJEZyWgzJHAw6QnA6UvKLyeQkdAHWDK8ZUqnijbyFi/HhpnkAlCV5B/qQk0dISZ8o82dEegP+GegK7pFULZoScz59zYjBEjuPR505PMK6aCTR//ZMl99dpZVlk5eTW/iFKWiPAjyL7g1kMPXS9WXp2u2+HcrStbuyTTm6/2qEDSbLPsr9eQowmvD5g/n4+AsAAP//AwB2C7Rx/wQAAA==", + "encoding": "utf-8", + "string": "" + }, + "headers": { + "CF-Cache-Status": [ + "DYNAMIC" + ], + "CF-RAY": [ + "8470acab8df65c3e-FRA" + ], + "Connection": [ + "keep-alive" + ], + "Content-Encoding": [ + "gzip" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Wed, 17 Jan 2024 18:31:19 GMT" + ], + "NEL": [ + "{\"success_fraction\":0.01,\"report_to\":\"cf-nel\",\"max_age\":604800}" + ], + "Report-To": [ + "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=1k3Mw%2BLGvzmPSdKKAS8CGDKhwugmnfzoexAC6CAgwhtUG%2FF3FwJxFnE1JMQzrWiehoabhqh85pHKRjewUY6LVYhSrtpDZaEjmjVc6QZokwbwzpCuVGkoXJxxR7JpzPKaq4C2%2FguiFQ%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}" + ], + "Server": [ + "cloudflare" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Zendesk-Zorg": [ + "yes" + ], + "cache-control": [ + "max-age=0, private, must-revalidate" + ], + "content-security-policy-report-only": [ + "base-uri 'self'; script-src 'self' https: *.zende.sk *.zendesk.com 'unsafe-eval' 'report-sample' *.googletagmanager.com tagmanager.google.com *.zdassets.com static.zdassets.com 'nonce-CQknu6qEJWapZohcDAiKGg=='; img-src 'self' https: *.zende.sk *.zendesk.com data: blob: *.zdusercontent.com *.apps.zdusercontent.com *.google-analytics.com *.googletagmanager.com ssl.gstatic.com *.gstatic.com *.zdassets.com *.g.doubleclick.net; font-src 'self' https: *.zende.sk *.zendesk.com data: fonts.gstatic.com *.typekit.net *.googleusercontent.com cdn.jsdelivr.net; media-src 'self' https: *.zende.sk *.zendesk.com blob: data:; worker-src 'self' https: *.zende.sk *.zendesk.com blob:; connect-src 'self' https: *.zende.sk *.zendesk.com ws: wss: api.segment.io; child-src 'self' https: *.zende.sk *.zendesk.com; object-src 'self' https: *.zende.sk *.zendesk.com; report-uri https://zendesk-eu.my.sentry.io/api/299/security/?sentry_key=283035aa3c1947e2ba44514578764430&sentry_environment=production" + ], + "etag": [ + "W/\"c27dbec3647d598be6f4d0f642e151b5\"" + ], + "set-cookie": [ + "_voice_session=WnNBMWF4anRGUXdIK25jNXZGcThWdmk5YjdSZjhHa0xPdG9HbUNVYnYyNHZ3L0NqVnBqNnhCdGRDcjBqZm45WTJYTVNRK2RCdG9WNDFMNkdKRWNmRmRHdi9DcHplczU5TkNCTXE2TVA0M0d2aGxnb3FJV1N6dDdRYXVtYlkxMkhnOWFFbVdtdi9URCs4QWdtbUdtUHRnPT0tLTdxVEpmNndjUDlrZWlkWWhUL2tBWXc9PQ%3D%3D--73f7ca77c36a680fd68634cc1972d7e81bcdc0b9; path=/; secure; HttpOnly" + ], + "strict-transport-security": [ + "max-age=31536000; includeSubDomains" + ], + "x-frame-options": [ + "SAMEORIGIN" + ], + "x-rate-limit": [ + "700" + ], + "x-rate-limit-remaining": [ + "700" + ], + "x-request-id": [ + "8470acab8df65c3e-FRA", + "8470acab8df65c3e-FRA" + ], + "x-runtime": [ + "0.143876" + ], + "x-xss-protection": [ + "1; mode=block" + ], + "x-zendesk-api-version": [ + "v2" + ], + "x-zendesk-origin-server": [ + "voice-app-server-59c955f4bd-lzp27" + ] + }, + "status": { + "code": 200, + "message": "OK" + }, + "url": "https://d3v-zenpydev.zendesk.com/api/v2/calls/16444799132306" + } + } + ], + "recorded_with": "betamax/0.8.1" +} \ No newline at end of file diff --git a/tests/test_api/betamax/TestCallPE.test_create_with_comment-.json b/tests/test_api/betamax/TestCallPE.test_create_with_comment-.json new file mode 100644 index 00000000..afbb5e43 --- /dev/null +++ b/tests/test_api/betamax/TestCallPE.test_create_with_comment-.json @@ -0,0 +1,125 @@ +{ + "http_interactions": [ + { + "recorded_at": "2024-01-17T18:31:20", + "request": { + "body": { + "encoding": "utf-8", + "string": "{\"call\": {\"app_id\": 1003048, \"call_ended_at\": \"2022-01-27T15:32:40+01\", \"call_started_at\": \"2022-01-27T15:31:40+01\", \"direction\": \"inbound\", \"from_line\": \"+183808333456\", \"from_line_nickname\": \"Sales\", \"to_line\": \"+149488484873\", \"to_line_nickname\": \"Technical Support\"}, \"comment\": {\"call_fields\": [\"from_line\", \"to_line\", \"call_started_at\"], \"title\": \"This is a ticket comment 2\"}}" + }, + "headers": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "Authorization": [ + "Basic " + ], + "Connection": [ + "keep-alive" + ], + "Content-Length": [ + "383" + ], + "Content-Type": [ + "application/json" + ], + "Cookie": [ + "__cfruid=0944e55a485c8c5f24050fecca77e3fa756ae200-1705516278; _cfuvid=ntaBUGPVn3_BNpVH.hqGqLPVQ5tge0gJoHDurVlT0vA-1705516278362-0-604800000; _voice_session=WnNBMWF4anRGUXdIK25jNXZGcThWdmk5YjdSZjhHa0xPdG9HbUNVYnYyNHZ3L0NqVnBqNnhCdGRDcjBqZm45WTJYTVNRK2RCdG9WNDFMNkdKRWNmRmRHdi9DcHplczU5TkNCTXE2TVA0M0d2aGxnb3FJV1N6dDdRYXVtYlkxMkhnOWFFbVdtdi9URCs4QWdtbUdtUHRnPT0tLTdxVEpmNndjUDlrZWlkWWhUL2tBWXc9PQ%3D%3D--73f7ca77c36a680fd68634cc1972d7e81bcdc0b9" + ], + "User-Agent": [ + "Zenpy/2.0.45" + ] + }, + "method": "POST", + "uri": "https://d3v-zenpydev.zendesk.com/api/v2/calls.json" + }, + "response": { + "body": { + "encoding": "utf-8", + "string": "{\"call\":{\"id\":16444799156882,\"account_id\":850916,\"ticket_id\":43278,\"end_user_id\":null,\"organization_id\":null,\"brand_id\":null,\"app_id\":1003048,\"agent_id\":null,\"from_line\":\"+183808333456\",\"from_line_nickname\":\"Sales\",\"to_line\":\"+149488484873\",\"to_line_nickname\":\"Technical Support\",\"call_started_at\":\"2022-01-27T14:31:40Z\",\"call_ended_at\":\"2022-01-27T14:32:40Z\",\"direction\":\"inbound\",\"duration\":null,\"dnis\":null,\"phone_name\":null,\"call_type\":null,\"end_user_location\":null,\"callback\":null,\"callback_source\":null,\"callback_number\":null,\"wait_time\":null,\"time_to_answer\":null,\"queue_time\":null,\"exceeded_queue_time\":null,\"queue_name\":null,\"ivr_time_spent\":null,\"ivr_hops\":null,\"ivr_action\":null,\"ivr_routed_to\":null,\"ivr_destination_group_name\":null,\"talk_time\":null,\"hold_time\":null,\"consultation_time\":null,\"wrap_up_time\":null,\"completion_status\":null,\"call_abandoned_position\":null,\"customer_requested_voicemail\":null,\"voicemail\":null,\"outside_business_hours\":null,\"overflowed\":null,\"overflowed_to\":null,\"call_disposition\":null,\"recording_url\":null,\"recording_time\":null,\"not_recording_time\":null,\"call_recording_consent_action\":null,\"recording_control_interactions\":null,\"created_at\":\"2024-01-17T18:31:20Z\",\"updated_at\":\"2024-01-17T18:31:20Z\",\"transcript\":null,\"external_id\":null}}" + }, + "headers": { + "CF-Cache-Status": [ + "DYNAMIC" + ], + "CF-RAY": [ + "8470acada8125c3e-FRA" + ], + "Connection": [ + "keep-alive" + ], + "Content-Type": [ + "application/json; charset=utf-8" + ], + "Date": [ + "Wed, 17 Jan 2024 18:31:20 GMT" + ], + "NEL": [ + "{\"success_fraction\":0.01,\"report_to\":\"cf-nel\",\"max_age\":604800}" + ], + "Report-To": [ + "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=62hFpk43lmKw6Fc0iBH8ihVzMUyHeyceERuFgkiPPuSfVLJl8nki5LuMH17t0z3%2BZKhIE2dR%2FCSaa%2FIk9KfMbHQfVMjdxWLDB3oOteKb8TvSmDwBUn0Y%2BTTiS1LARQ0q63OrKP8RMg%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}" + ], + "Server": [ + "cloudflare" + ], + "Transfer-Encoding": [ + "chunked" + ], + "X-Zendesk-Zorg": [ + "yes" + ], + "cache-control": [ + "max-age=0, private, must-revalidate" + ], + "content-security-policy-report-only": [ + "base-uri 'self'; script-src 'self' https: *.zende.sk *.zendesk.com 'unsafe-eval' 'report-sample' *.googletagmanager.com tagmanager.google.com *.zdassets.com static.zdassets.com 'nonce-D99pRzXKwz9sjyKtmFSrmg=='; img-src 'self' https: *.zende.sk *.zendesk.com data: blob: *.zdusercontent.com *.apps.zdusercontent.com *.google-analytics.com *.googletagmanager.com ssl.gstatic.com *.gstatic.com *.zdassets.com *.g.doubleclick.net; font-src 'self' https: *.zende.sk *.zendesk.com data: fonts.gstatic.com *.typekit.net *.googleusercontent.com cdn.jsdelivr.net; media-src 'self' https: *.zende.sk *.zendesk.com blob: data:; worker-src 'self' https: *.zende.sk *.zendesk.com blob:; connect-src 'self' https: *.zende.sk *.zendesk.com ws: wss: api.segment.io; child-src 'self' https: *.zende.sk *.zendesk.com; object-src 'self' https: *.zende.sk *.zendesk.com; report-uri https://zendesk-eu.my.sentry.io/api/299/security/?sentry_key=283035aa3c1947e2ba44514578764430&sentry_environment=production" + ], + "etag": [ + "W/\"a05b4720bd8c10d26cd4f409e2fa7815\"" + ], + "set-cookie": [ + "_voice_session=TnRyT3huYzBOWFpGY2xtZWpaUWhOMkFGMmdUUW44ckFOTFhtZlNSOVoxb3M1WCtJUXZydGFVTzc2K0Q3QnNmOTRYNlIrRTVBN2o0RzlrK1dXWHl6VVVwbUxKZFEzWlZGZ01GcWxHVzVaQmtiZG5wK0xGc0s3L1hjaFhjQXNHRUdHNlZVRzhYZWNPaWIzUlpQWGJQRnBnPT0tLUdUS01mS2E2NkcwM05mci9qbjVhOEE9PQ%3D%3D--043ea541b31631d1f4e2ff1e291626d16e38b917; path=/; secure; HttpOnly" + ], + "strict-transport-security": [ + "max-age=31536000; includeSubDomains" + ], + "x-frame-options": [ + "SAMEORIGIN" + ], + "x-rate-limit": [ + "700" + ], + "x-rate-limit-remaining": [ + "700" + ], + "x-request-id": [ + "8470acada8125c3e-FRA", + "8470acada8125c3e-FRA" + ], + "x-runtime": [ + "0.702614" + ], + "x-xss-protection": [ + "1; mode=block" + ], + "x-zendesk-api-version": [ + "v2" + ], + "x-zendesk-origin-server": [ + "voice-app-server-59c955f4bd-cff2w" + ] + }, + "status": { + "code": 201, + "message": "Created" + }, + "url": "https://d3v-zenpydev.zendesk.com/api/v2/calls.json" + } + } + ], + "recorded_with": "betamax/0.8.1" +} \ No newline at end of file diff --git a/tests/test_api/test_calls_pe.py b/tests/test_api/test_calls_pe.py new file mode 100644 index 00000000..f6fc55f1 --- /dev/null +++ b/tests/test_api/test_calls_pe.py @@ -0,0 +1,87 @@ +from test_api.fixtures import ZenpyApiTestCase + +from zenpy.lib.api_objects.talk_objects import ( + CallPe, + VoiceComment +) + +TEST_APP_ID = 1003048 + +class TestCallPE( + ZenpyApiTestCase +): + __test__ = True + ZenpyType = CallPe + + def test_create_update_get(self): + cassette_name = "{}".format(self.generate_cassette_name()) + with self.recorder.use_cassette( + cassette_name=cassette_name, serialize_with="prettyjson" + ): + call = CallPe( + app_id=TEST_APP_ID, + call_ended_at="2022-01-27T15:32:40+01", + call_started_at="2022-01-27T15:31:40+01", + direction="inbound", + from_line="+183808333456", + from_line_nickname="Sales", + to_line="+149488484873", + to_line_nickname="Technical Support" + ) + call_created = self.zenpy_client.calls.create(call) + self.assertIsInstance(call_created, self.ZenpyType) + + call_upd = CallPe( + id=call_created.id, + call_ended_at="2022-04-16T09:15:37Z" + ) + call_updated = self.zenpy_client.calls.update(call_upd) + self.assertIsInstance(call_updated, self.ZenpyType) + + call_requested = self.zenpy_client.calls(id=call_updated.id) + self.assertIsInstance(call_requested, self.ZenpyType) + + def test_create_with_comment(self): + cassette_name = "{}-".format(self.generate_cassette_name()) + with self.recorder.use_cassette( + cassette_name=cassette_name, serialize_with="prettyjson" + ): + call = CallPe( + app_id=TEST_APP_ID, + call_ended_at="2022-01-27T15:32:40+01", + call_started_at="2022-01-27T15:31:40+01", + direction="inbound", + from_line="+183808333456", + from_line_nickname="Sales", + to_line="+149488484873", + to_line_nickname="Technical Support" + ) + comment = VoiceComment( + call_fields=["from_line", "to_line", "call_started_at"], + title="This is a ticket comment 2" + ) + call_created = self.zenpy_client.calls.create(call, comment) + self.assertIsInstance(call_created, self.ZenpyType) + + def test_add_comment(self): + cassette_name = "{}".format(self.generate_cassette_name()) + with self.recorder.use_cassette( + cassette_name=cassette_name, serialize_with="prettyjson" + ): + call = CallPe( + app_id=TEST_APP_ID, + call_ended_at="2022-01-27T15:32:40+01", + call_started_at="2022-01-27T15:31:40+01", + direction="inbound", + from_line="+183808333456", + from_line_nickname="Sales", + to_line="+149488484873", + to_line_nickname="Technical Support" + ) + call_res = self.zenpy_client.calls.create(call) + comment = VoiceComment( + call_fields=["direction", ], + title="This is a ticket comment 2" + ) + result = self.zenpy_client.calls.comment(call_res, comment) + self.assertEqual(result["type"], "TpeVoiceComment") diff --git a/zenpy/__init__.py b/zenpy/__init__.py index 25e30c5b..235fcdd0 100644 --- a/zenpy/__init__.py +++ b/zenpy/__init__.py @@ -38,6 +38,7 @@ SkipApi, TalkApi, TalkPEApi, + CallsPEApi, CustomAgentRolesApi, SearchApi, SearchExportApi, @@ -173,6 +174,7 @@ def __init__( self.targets = TargetApi(config, object_type="target") self.talk = TalkApi(config) self.talk_pe = TalkPEApi(config) + self.calls = CallsPEApi(config) self.custom_agent_roles = CustomAgentRolesApi( config, object_type="custom_agent_role" ) diff --git a/zenpy/lib/api.py b/zenpy/lib/api.py index 314f83a8..1f38ee16 100644 --- a/zenpy/lib/api.py +++ b/zenpy/lib/api.py @@ -17,11 +17,13 @@ from zenpy.lib.api_objects.help_centre_objects import ( Section, Article, Comment, ArticleAttachment, Label, Category, Translation, Topic, Post, Subscription) +from zenpy.lib.api_objects.talk_objects import ( + CallPe, VoiceComment) from zenpy.lib.exception import RatelimitBudgetExceeded, APIException, \ RecordNotFoundException, SearchResponseLimitExceeded from zenpy.lib.mapping import ZendeskObjectMapping, \ ChatObjectMapping, HelpCentreObjectMapping, \ - TalkObjectMapping + TalkObjectMapping, CallPEObjectMapping from zenpy.lib.request import AccessPolicyRequest, AccountRequest, AgentRequest, \ CRUDRequest, ChatApiRequest, HelpCentreRequest, \ HelpdeskAttachmentRequest, HelpdeskCommentRequest, \ @@ -42,7 +44,8 @@ SlaPolicyResponseHandler, TriggerResponseHandler, \ VisitorResponseHandler, WebhookInvocationAttemptsResponseHandler, \ WebhookInvocationsResponseHandler, \ - WebhooksResponseHandler, ZISIntegrationResponseHandler + WebhooksResponseHandler, ZISIntegrationResponseHandler, \ + VoiceCommentResponseHandler from zenpy.lib.util import dict_clean, as_plural, extract_id, \ is_iterable_but_not_string, json_encode_for_zendesk, \ @@ -93,6 +96,7 @@ def __init__(self, subdomain, session, timeout, ratelimit, WebhookInvocationsResponseHandler, WebhookInvocationAttemptsResponseHandler, WebhooksResponseHandler, + VoiceCommentResponseHandler, GenericZendeskResponseHandler, HTTPOKResponseHandler, ) @@ -2831,6 +2835,42 @@ def create_ticket(self, agent, ticket): return self._post(url, payload=payload) +class CallsPEApi(Api): + def __init__(self, config): + super(CallsPEApi, self).__init__(config, + object_type='call', + endpoint=EndpointFactory('calls')) + + self._object_mapping = CallPEObjectMapping(self) + + def __call__(self, *args, **kwargs): + if 'id' not in kwargs: + raise ZenpyException("Get a call endpoint requires an id") + url = self._build_url(self.endpoint(id=kwargs["id"])) + return self._get(url) + + def create(self, call, comment=None): + + payload = {"call": self._serialize(call)} + if comment: + payload["comment"] = self._serialize(comment) + + url = self._build_url(self.endpoint.create()) + return self._post(url, payload) + + def update(self, call): + payload = {"call": self._serialize(call)} + url = self._build_url(self.endpoint.update(id=call.id)) + return self._patch(url, payload) + + @extract_id(CallPe) + def comment(self, call, comment): + + payload = self._serialize(comment) + + url = self._build_url(self.endpoint.comment(id=call)) + return self._post(url, payload) + class CustomAgentRolesApi(CRUDApi): pass diff --git a/zenpy/lib/api_objects/talk_objects.py b/zenpy/lib/api_objects/talk_objects.py index 33c33c5b..0edbf816 100644 --- a/zenpy/lib/api_objects/talk_objects.py +++ b/zenpy/lib/api_objects/talk_objects.py @@ -734,3 +734,268 @@ def state(self, state): if state: self.state_id = state.id self._state = state + + +class CallPe(BaseObject): + """ + ###################################################################### + # Do not modify, this class is autogenerated by gen_classes.py # + ###################################################################### + """ + + def __init__(self, + api=None, + agent_id=None, + app_id=None, + brand_id=None, + call_disposition=None, + call_ended_at=None, + call_recording_consent=None, + call_recording_consent_action=None, + call_started_at=None, + call_type=None, + callback_number=None, + callback_source=None, + completion_status=None, + consultation_time=None, + customer_requested_voicemail=None, + direction=None, + dnis=None, + duration=None, + end_user_id=None, + end_user_location=None, + exceeded_queue_time=None, + external_id=None, + from_line=None, + from_line_nickname=None, + hold_time=None, + id=None, + ivr_action=None, + ivr_destination_group_name=None, + ivr_hops=None, + ivr_routed_to=None, + ivr_time_spent=None, + not_recording_time=None, + outside_business_hours=None, + overflowed=None, + overflowed_to=None, + phone_name=None, + queue_name=None, + queue_time=None, + recording_control_interactions=None, + recording_time=None, + recording_url=None, + talk_time=None, + ticket_id=None, + time_to_answer=None, + to_line=None, + to_line_nickname=None, + transcript=None, + voicemail=None, + wait_time=None, + wrap_up_time=None, + **kwargs): + + self.api = api + self.agent_id = agent_id + self.app_id = app_id + self.brand_id = brand_id + self.call_disposition = call_disposition + self.call_ended_at = call_ended_at + self.call_recording_consent = call_recording_consent + self.call_recording_consent_action = call_recording_consent_action + self.call_started_at = call_started_at + self.call_type = call_type + self.callback_number = callback_number + self.callback_source = callback_source + self.completion_status = completion_status + self.consultation_time = consultation_time + self.customer_requested_voicemail = customer_requested_voicemail + self.direction = direction + self.dnis = dnis + self.duration = duration + self.end_user_id = end_user_id + self.end_user_location = end_user_location + self.exceeded_queue_time = exceeded_queue_time + self.external_id = external_id + self.from_line = from_line + self.from_line_nickname = from_line_nickname + self.hold_time = hold_time + self.id = id + self.ivr_action = ivr_action + self.ivr_destination_group_name = ivr_destination_group_name + self.ivr_hops = ivr_hops + self.ivr_routed_to = ivr_routed_to + self.ivr_time_spent = ivr_time_spent + self.not_recording_time = not_recording_time + self.outside_business_hours = outside_business_hours + self.overflowed = overflowed + self.overflowed_to = overflowed_to + self.phone_name = phone_name + self.queue_name = queue_name + self.queue_time = queue_time + self.recording_control_interactions = recording_control_interactions + self.recording_time = recording_time + self.recording_url = recording_url + self.talk_time = talk_time + self.ticket_id = ticket_id + self.time_to_answer = time_to_answer + self.to_line = to_line + self.to_line_nickname = to_line_nickname + self.transcript = transcript + self.voicemail = voicemail + self.wait_time = wait_time + self.wrap_up_time = wrap_up_time + + for key, value in kwargs.items(): + setattr(self, key, value) + + for key in self.to_dict(): + if getattr(self, key) is None: + try: + self._dirty_attributes.remove(key) + except KeyError: + continue + + @property + def agent(self): + + if self.api and self.agent_id: + return self.api._get_agent(self.agent_id) + + @agent.setter + def agent(self, agent): + if agent: + self.agent_id = agent.id + self._agent = agent + + @property + def app(self): + + if self.api and self.app_id: + return self.api._get_app(self.app_id) + + @app.setter + def app(self, app): + if app: + self.app_id = app.id + self._app = app + + @property + def brand(self): + + if self.api and self.brand_id: + return self.api._get_brand(self.brand_id) + + @brand.setter + def brand(self, brand): + if brand: + self.brand_id = brand.id + self._brand = brand + + @property + def call_ended(self): + + if self.call_ended_at: + return dateutil.parser.parse(self.call_ended_at) + + @call_ended.setter + def call_ended(self, call_ended): + if call_ended: + self.call_ended_at = call_ended + + @property + def call_started(self): + + if self.call_started_at: + return dateutil.parser.parse(self.call_started_at) + + @call_started.setter + def call_started(self, call_started): + if call_started: + self.call_started_at = call_started + + @property + def end_user(self): + + if self.api and self.end_user_id: + return self.api._get_end_user(self.end_user_id) + + @end_user.setter + def end_user(self, end_user): + if end_user: + self.end_user_id = end_user.id + self._end_user = end_user + + @property + def ticket(self): + + if self.api and self.ticket_id: + return self.api._get_ticket(self.ticket_id) + + @ticket.setter + def ticket(self, ticket): + if ticket: + self.ticket_id = ticket.id + self._ticket = ticket + + +class VoiceComment(BaseObject): + """ + ###################################################################### + # Do not modify, this class is autogenerated by gen_classes.py # + ###################################################################### + """ + + def __init__(self, + api=None, + author_id=None, + call_fields=None, + display_to_agent=None, + end_user_id=None, + subject=None, + title=None, + **kwargs): + + self.api = api + self.author_id = author_id + self.call_fields = call_fields + self.display_to_agent = display_to_agent + self.end_user_id = end_user_id + self.subject = subject + self.title = title + + for key, value in kwargs.items(): + setattr(self, key, value) + + for key in self.to_dict(): + if getattr(self, key) is None: + try: + self._dirty_attributes.remove(key) + except KeyError: + continue + + @property + def author(self): + + if self.api and self.author_id: + return self.api._get_user(self.author_id) + + @author.setter + def author(self, author): + if author: + self.author_id = author.id + self._author = author + + @property + def end_user(self): + + if self.api and self.end_user_id: + return self.api._get_end_user(self.end_user_id) + + @end_user.setter + def end_user(self, end_user): + if end_user: + self.end_user_id = end_user.id + self._end_user = end_user + diff --git a/zenpy/lib/endpoint.py b/zenpy/lib/endpoint.py index 44d5cc37..39f80d60 100644 --- a/zenpy/lib/endpoint.py +++ b/zenpy/lib/endpoint.py @@ -782,6 +782,11 @@ class Dummy(object): talk_pe.create_ticket = PrimaryEndpoint( 'channels/voice/tickets.json') + calls = SecondaryEndpoint('calls/%(id)s') + calls.create = PrimaryEndpoint('calls') + calls.update = SecondaryEndpoint('calls/%(id)s') + calls.comment = SecondaryEndpoint('calls/%(id)s/comments.json') + help_centre = Dummy() help_centre.articles = PrimaryEndpoint('help_center/articles') help_centre.articles.create = SecondaryEndpoint( diff --git a/zenpy/lib/mapping.py b/zenpy/lib/mapping.py index e682c37d..bd2a4eb0 100644 --- a/zenpy/lib/mapping.py +++ b/zenpy/lib/mapping.py @@ -125,7 +125,9 @@ CurrentQueueActivity, Leg, PhoneNumbers, - ShowAvailability + ShowAvailability, + CallPe, + VoiceComment ) from zenpy.lib.api_objects.zis_objects import Integration from zenpy.lib.exception import ZenpyException @@ -397,3 +399,14 @@ class TalkObjectMapping(ZendeskObjectMapping): 'availability': ShowAvailability, 'leg': Leg } + + +class CallPEObjectMapping(ZendeskObjectMapping): + """ + Handle converting Talk PE/Calls API objects to Python ones. This class exists + to prevent namespace collisions between APIs. + """ + class_mapping = { + 'call': CallPe, + 'voice_comment': VoiceComment + } diff --git a/zenpy/lib/response.py b/zenpy/lib/response.py index 228fca64..7570ac04 100644 --- a/zenpy/lib/response.py +++ b/zenpy/lib/response.py @@ -618,3 +618,20 @@ def build(self, response): def deserialize(self, response_json): return response_json['locales'] + + +class VoiceCommentResponseHandler(GenericZendeskResponseHandler): + @staticmethod + def applies_to(api, response): + try: + response_json = response.json() + + return get_endpoint_path(api, response).startswith('/calls') \ + and 'type' in response_json \ + and response_json['type'] == 'TpeVoiceComment' + + except ValueError: + return False + + def build(self, response): + return response.json()