sendbee-api 1.7.0__tar.gz → 1.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/PKG-INFO +23 -29
  2. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/README.md +1 -19
  3. sendbee_api-1.7.2/pyproject.toml +46 -0
  4. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/bind.py +11 -1
  5. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/constants.py +21 -0
  6. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/conversations/models.py +2 -0
  7. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/conversations/query_params.py +2 -0
  8. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/debug.py +8 -1
  9. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/PKG-INFO +24 -30
  10. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/SOURCES.txt +11 -2
  11. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/requires.txt +6 -1
  12. sendbee_api-1.7.2/tests/test_auth.py +110 -0
  13. sendbee_api-1.7.2/tests/test_bind_request.py +476 -0
  14. sendbee_api-1.7.2/tests/test_client.py +67 -0
  15. sendbee_api-1.7.2/tests/test_endpoints_smoke.py +177 -0
  16. sendbee_api-1.7.2/tests/test_fields.py +105 -0
  17. sendbee_api-1.7.2/tests/test_formatter.py +79 -0
  18. sendbee_api-1.7.2/tests/test_models.py +99 -0
  19. sendbee_api-1.7.2/tests/test_query_params.py +59 -0
  20. sendbee_api-1.7.2/tests/test_response.py +112 -0
  21. sendbee_api-1.7.0/setup.py +0 -47
  22. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/LICENSE +0 -0
  23. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/__init__.py +0 -0
  24. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/auth.py +0 -0
  25. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/automation/__init__.py +0 -0
  26. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/automation/client.py +0 -0
  27. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/automation/models.py +0 -0
  28. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/automation/query_params.py +0 -0
  29. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/client.py +0 -0
  30. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/contacts/__init__.py +0 -0
  31. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/contacts/client.py +0 -0
  32. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/contacts/models.py +0 -0
  33. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/contacts/query_params.py +0 -0
  34. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/conversations/__init__.py +0 -0
  35. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/conversations/client.py +0 -0
  36. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/exceptions.py +0 -0
  37. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/fields.py +0 -0
  38. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/formatter.py +0 -0
  39. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/models.py +0 -0
  40. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/query_params.py +0 -0
  41. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/rate_limit/__init__.py +0 -0
  42. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/rate_limit/client.py +0 -0
  43. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/rate_limit/models.py +0 -0
  44. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/rate_limit/query_params.py +0 -0
  45. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/response.py +0 -0
  46. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/teams/__init__.py +0 -0
  47. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/teams/client.py +0 -0
  48. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/teams/models.py +0 -0
  49. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api/teams/query_params.py +0 -0
  50. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/dependency_links.txt +0 -0
  51. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/sendbee_api.egg-info/top_level.txt +0 -0
  52. {sendbee_api-1.7.0 → sendbee_api-1.7.2}/setup.cfg +0 -0
@@ -1,14 +1,12 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: sendbee_api
3
- Version: 1.7.0
3
+ Version: 1.7.2
4
4
  Summary: Python client SDK for Sendbee Public API
5
- Home-page: https://github.com/sendbee/sendbee-python-client
6
- Author: Sendbee ltd
7
- Author-email: info@sendbee.io
5
+ Author-email: Sendbee ltd <info@sendbee.io>
8
6
  License: MIT
7
+ Project-URL: Homepage, https://github.com/sendbee/sendbee-python-client
9
8
  Project-URL: Source, https://github.com/sendbee/sendbee-python-client
10
- Keywords: sendbee api python
11
- Platform: UNKNOWN
9
+ Keywords: sendbee,api,python
12
10
  Classifier: Intended Audience :: Developers
13
11
  Classifier: Topic :: Software Development :: Build Tools
14
12
  Classifier: License :: OSI Approved :: MIT License
@@ -16,30 +14,28 @@ Classifier: Programming Language :: Python :: 3.6
16
14
  Classifier: Programming Language :: Python :: 3.7
17
15
  Classifier: Programming Language :: Python :: 3.8
18
16
  Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.6
19
22
  Description-Content-Type: text/markdown
20
23
  License-File: LICENSE
21
-
22
- # Sendbee Python API Client
23
-
24
- ```
25
-
26
- .' '. __
27
- . . . (__\_
28
- . . . -{{_(|8)
29
- ' . . ' ' . . ' (__/
30
-
31
- ```
24
+ Requires-Dist: click>=7.0
25
+ Requires-Dist: requests>=2.20.0
26
+ Requires-Dist: dumpit>=0.5.0
27
+ Requires-Dist: aenum>=2.1.2
28
+ Requires-Dist: ujson<6,>=5.7.0
29
+ Requires-Dist: cryptography>=3.2
30
+ Requires-Dist: curlify>=2.2.1
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: responses>=0.23; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # AI Number Python API Client
32
37
 
33
38
  [![PyPI version](https://badge.fury.io/py/sendbee-api.svg)](https://badge.fury.io/py/sendbee-api)
34
- [![Build Status](https://travis-ci.org/sendbee/sendbee-python-api-client.svg?branch=master)](https://travis-ci.org/sendbee/sendbee-python-api-client)
35
-
36
- ![GitHub issues](https://img.shields.io/github/issues/sendbee/sendbee-python-api-client.svg)
37
- ![GitHub closed issues](https://img.shields.io/github/issues-closed/sendbee/sendbee-python-api-client.svg)
38
- ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/sendbee/sendbee-python-api-client.svg)
39
-
40
- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sendbee-api.svg)
41
- ![GitHub](https://img.shields.io/github/license/sendbee/sendbee-python-api-client.svg?color=blue)
42
- ![GitHub last commit](https://img.shields.io/github/last-commit/sendbee/sendbee-python-api-client.svg?color=blue)
43
39
 
44
40
  ## Table of contents
45
41
 
@@ -692,5 +688,3 @@ api = SendbeeApi(
692
688
  Once you enabled the internal debug tool, every request to API will output various request and response data in standard output:
693
689
 
694
690
  ![Debugging](docs/images/debugging.png)
695
-
696
-
@@ -1,24 +1,6 @@
1
- # Sendbee Python API Client
2
-
3
- ```
4
-
5
- .' '. __
6
- . . . (__\_
7
- . . . -{{_(|8)
8
- ' . . ' ' . . ' (__/
9
-
10
- ```
1
+ # AI Number Python API Client
11
2
 
12
3
  [![PyPI version](https://badge.fury.io/py/sendbee-api.svg)](https://badge.fury.io/py/sendbee-api)
13
- [![Build Status](https://travis-ci.org/sendbee/sendbee-python-api-client.svg?branch=master)](https://travis-ci.org/sendbee/sendbee-python-api-client)
14
-
15
- ![GitHub issues](https://img.shields.io/github/issues/sendbee/sendbee-python-api-client.svg)
16
- ![GitHub closed issues](https://img.shields.io/github/issues-closed/sendbee/sendbee-python-api-client.svg)
17
- ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/sendbee/sendbee-python-api-client.svg)
18
-
19
- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sendbee-api.svg)
20
- ![GitHub](https://img.shields.io/github/license/sendbee/sendbee-python-api-client.svg?color=blue)
21
- ![GitHub last commit](https://img.shields.io/github/last-commit/sendbee/sendbee-python-api-client.svg?color=blue)
22
4
 
23
5
  ## Table of contents
24
6
 
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sendbee_api"
7
+ version = "1.7.2"
8
+ description = "Python client SDK for Sendbee Public API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.6"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Sendbee ltd", email = "info@sendbee.io" }]
13
+ keywords = ["sendbee", "api", "python"]
14
+ classifiers = [
15
+ "Intended Audience :: Developers",
16
+ "Topic :: Software Development :: Build Tools",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3.6",
19
+ "Programming Language :: Python :: 3.7",
20
+ "Programming Language :: Python :: 3.8",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ ]
27
+ dependencies = [
28
+ "click>=7.0",
29
+ "requests>=2.20.0",
30
+ "dumpit>=0.5.0",
31
+ "aenum>=2.1.2",
32
+ "ujson>=5.7.0,<6",
33
+ "cryptography>=3.2",
34
+ "curlify>=2.2.1",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ dev = ["pytest", "responses>=0.23"]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/sendbee/sendbee-python-client"
42
+ Source = "https://github.com/sendbee/sendbee-python-client"
43
+
44
+ [tool.setuptools.packages.find]
45
+ include = ["sendbee_api*"]
46
+ exclude = ["tests*"]
@@ -190,6 +190,10 @@ def bind_request(**request_data):
190
190
  self.parameters[constants.RequestConst.QUERY]
191
191
  )
192
192
  self.debug.ok(constants.ResponseConst.RESPONSE_OBJECT, response)
193
+ self.debug.set_curl(
194
+ constants.DebugConst.CURL,
195
+ response.request
196
+ )
193
197
 
194
198
  return response.status_code, response.headers, response.text
195
199
 
@@ -213,7 +217,13 @@ def bind_request(**request_data):
213
217
  self.debug.ok('payload', self.parameters[
214
218
  constants.RequestConst.QUERY
215
219
  ])
216
- self.debug.ok(constants.ResponseConst.RESPONSE_OBJECT, response)
220
+ self.debug.ok(
221
+ constants.ResponseConst.RESPONSE_OBJECT, response
222
+ )
223
+ self.debug.set_curl(
224
+ constants.DebugConst.CURL,
225
+ response.request
226
+ )
217
227
 
218
228
  return response.status_code, response.headers, response.text
219
229
 
@@ -1,3 +1,4 @@
1
+ from abc import ABCMeta
1
2
  from collections import namedtuple
2
3
 
3
4
 
@@ -64,3 +65,23 @@ ResponseDataConst = namedtuple(
64
65
  'ResponseDataConst', ['DATA', 'META']
65
66
  )('data', 'meta')
66
67
 
68
+
69
+ class ConstMeta(ABCMeta):
70
+ def __setattr__(self, name, value):
71
+ pass
72
+
73
+
74
+ class Const(metaclass=ConstMeta):
75
+ """Helper class """
76
+ __slots__ = ()
77
+
78
+
79
+ class DebugConst(Const):
80
+ PARAMETERS = 'parameters'
81
+ RESPONSE = 'response'
82
+ STATUS_CODE = 'status code'
83
+ FINAL_URL = 'final url'
84
+ HEADERS = 'headers'
85
+ METHOD = 'method'
86
+ QUERY_PARAMETERS = 'query parameters'
87
+ CURL = 'curl'
@@ -41,6 +41,8 @@ class SentMessage(Model):
41
41
 
42
42
  _conversation_id = TextField(index='id', desc='Conversation UUID')
43
43
  _message_id = TextField(index='message_id', desc='Message UUID')
44
+ _message_reference_id = TextField(index='message_reference_id',
45
+ desc='Message Reference UUID')
44
46
  _status = TextField(index='status', desc='Message status')
45
47
 
46
48
 
@@ -21,6 +21,7 @@ class SendTemplateMessage(QueryParams):
21
21
  prevent_bot_off = 'prevent_bot_off', 'Prevent turning-off chatbot'
22
22
  agent_id = 'agent_id', 'Assigned agent for the conversation'
23
23
  attachment = 'attachment', 'Attachment URL for media templates'
24
+ live_inbox = 'live_inbox', 'Trigger real-time events after sending'
24
25
 
25
26
 
26
27
  class SendMessage(QueryParams):
@@ -31,6 +32,7 @@ class SendMessage(QueryParams):
31
32
  media_url = 'media_url', 'Media URL for media message'
32
33
  prevent_bot_off = 'prevent_bot_off', 'Prevent turning-off chatbot'
33
34
  agent_id = 'agent_id', 'Assigned agent for the conversation'
35
+ live_inbox = 'live_inbox', 'Trigger real-time events after sending'
34
36
 
35
37
 
36
38
  class ListConversations(QueryParams):
@@ -1,5 +1,5 @@
1
1
  import click
2
- import pprint
2
+ import curlify
3
3
 
4
4
 
5
5
  STATUS_OK = 'ok'
@@ -25,6 +25,13 @@ class Debug:
25
25
  def error(self, key, value):
26
26
  self.record(key, value, STATUS_ERROR)
27
27
 
28
+ def set_curl(self, key, request):
29
+ if self.client.debug:
30
+ try:
31
+ self.ok(key, curlify.to_curl(request, compressed=False))
32
+ except UnicodeDecodeError:
33
+ self.ok(key, '__unable_to_decode__')
34
+
28
35
  def record(self, key, value, status):
29
36
  if self.client.debug:
30
37
  self.recorded_data[key] = {'value': value, 'status': status}
@@ -1,14 +1,12 @@
1
- Metadata-Version: 2.1
2
- Name: sendbee-api
3
- Version: 1.7.0
1
+ Metadata-Version: 2.4
2
+ Name: sendbee_api
3
+ Version: 1.7.2
4
4
  Summary: Python client SDK for Sendbee Public API
5
- Home-page: https://github.com/sendbee/sendbee-python-client
6
- Author: Sendbee ltd
7
- Author-email: info@sendbee.io
5
+ Author-email: Sendbee ltd <info@sendbee.io>
8
6
  License: MIT
7
+ Project-URL: Homepage, https://github.com/sendbee/sendbee-python-client
9
8
  Project-URL: Source, https://github.com/sendbee/sendbee-python-client
10
- Keywords: sendbee api python
11
- Platform: UNKNOWN
9
+ Keywords: sendbee,api,python
12
10
  Classifier: Intended Audience :: Developers
13
11
  Classifier: Topic :: Software Development :: Build Tools
14
12
  Classifier: License :: OSI Approved :: MIT License
@@ -16,30 +14,28 @@ Classifier: Programming Language :: Python :: 3.6
16
14
  Classifier: Programming Language :: Python :: 3.7
17
15
  Classifier: Programming Language :: Python :: 3.8
18
16
  Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.6
19
22
  Description-Content-Type: text/markdown
20
23
  License-File: LICENSE
21
-
22
- # Sendbee Python API Client
23
-
24
- ```
25
-
26
- .' '. __
27
- . . . (__\_
28
- . . . -{{_(|8)
29
- ' . . ' ' . . ' (__/
30
-
31
- ```
24
+ Requires-Dist: click>=7.0
25
+ Requires-Dist: requests>=2.20.0
26
+ Requires-Dist: dumpit>=0.5.0
27
+ Requires-Dist: aenum>=2.1.2
28
+ Requires-Dist: ujson<6,>=5.7.0
29
+ Requires-Dist: cryptography>=3.2
30
+ Requires-Dist: curlify>=2.2.1
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: responses>=0.23; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # AI Number Python API Client
32
37
 
33
38
  [![PyPI version](https://badge.fury.io/py/sendbee-api.svg)](https://badge.fury.io/py/sendbee-api)
34
- [![Build Status](https://travis-ci.org/sendbee/sendbee-python-api-client.svg?branch=master)](https://travis-ci.org/sendbee/sendbee-python-api-client)
35
-
36
- ![GitHub issues](https://img.shields.io/github/issues/sendbee/sendbee-python-api-client.svg)
37
- ![GitHub closed issues](https://img.shields.io/github/issues-closed/sendbee/sendbee-python-api-client.svg)
38
- ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/sendbee/sendbee-python-api-client.svg)
39
-
40
- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/sendbee-api.svg)
41
- ![GitHub](https://img.shields.io/github/license/sendbee/sendbee-python-api-client.svg?color=blue)
42
- ![GitHub last commit](https://img.shields.io/github/last-commit/sendbee/sendbee-python-api-client.svg?color=blue)
43
39
 
44
40
  ## Table of contents
45
41
 
@@ -692,5 +688,3 @@ api = SendbeeApi(
692
688
  Once you enabled the internal debug tool, every request to API will output various request and response data in standard output:
693
689
 
694
690
  ![Debugging](docs/images/debugging.png)
695
-
696
-
@@ -1,6 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
- setup.py
3
+ pyproject.toml
4
4
  sendbee_api/__init__.py
5
5
  sendbee_api/auth.py
6
6
  sendbee_api/bind.py
@@ -37,4 +37,13 @@ sendbee_api/rate_limit/query_params.py
37
37
  sendbee_api/teams/__init__.py
38
38
  sendbee_api/teams/client.py
39
39
  sendbee_api/teams/models.py
40
- sendbee_api/teams/query_params.py
40
+ sendbee_api/teams/query_params.py
41
+ tests/test_auth.py
42
+ tests/test_bind_request.py
43
+ tests/test_client.py
44
+ tests/test_endpoints_smoke.py
45
+ tests/test_fields.py
46
+ tests/test_formatter.py
47
+ tests/test_models.py
48
+ tests/test_query_params.py
49
+ tests/test_response.py
@@ -2,5 +2,10 @@ click>=7.0
2
2
  requests>=2.20.0
3
3
  dumpit>=0.5.0
4
4
  aenum>=2.1.2
5
- ujson==2.0.1
5
+ ujson<6,>=5.7.0
6
6
  cryptography>=3.2
7
+ curlify>=2.2.1
8
+
9
+ [dev]
10
+ pytest
11
+ responses>=0.23
@@ -0,0 +1,110 @@
1
+ """Tests for SendbeeAuth: HMAC token generation and verification."""
2
+
3
+ import base64
4
+ import hmac
5
+ import hashlib
6
+ from datetime import datetime, timezone
7
+
8
+ import pytest
9
+
10
+ from sendbee_api.auth import SendbeeAuth
11
+
12
+
13
+ def test_get_auth_token_returns_base64_encoded_timestamp_dot_hmac():
14
+ """Token format is base64(<unix_ts>.<sha256_hmac_hex>)."""
15
+ auth = SendbeeAuth("secret")
16
+
17
+ token = auth.get_auth_token()
18
+
19
+ decoded = base64.b64decode(token).decode("utf-8")
20
+ timestamp, hex_digest = decoded.split(".")
21
+ assert timestamp.isdigit()
22
+ assert len(hex_digest) == 64
23
+ int(hex_digest, 16)
24
+
25
+
26
+ def test_get_auth_token_uses_hmac_sha256_of_timestamp_under_private_key(monkeypatch):
27
+ """HMAC payload is the timestamp string, key is the private key."""
28
+ fixed_ts = 1700000000
29
+ secret = "secret-bytes"
30
+
31
+ class _FrozenDatetime(datetime):
32
+ @classmethod
33
+ def now(cls, tz=None):
34
+ return datetime.fromtimestamp(fixed_ts, tz=tz)
35
+
36
+ monkeypatch.setattr("sendbee_api.auth.datetime", _FrozenDatetime)
37
+
38
+ expected_hmac = hmac.new(
39
+ secret.encode("utf-8"),
40
+ str(fixed_ts).encode("utf-8"),
41
+ hashlib.sha256,
42
+ ).hexdigest()
43
+
44
+ token = SendbeeAuth(secret).get_auth_token()
45
+
46
+ decoded = base64.b64decode(token).decode("utf-8")
47
+ timestamp, hex_digest = decoded.split(".")
48
+ assert timestamp == str(fixed_ts)
49
+ assert hex_digest == expected_hmac
50
+
51
+
52
+ def test_get_auth_token_changes_when_timestamp_changes(monkeypatch):
53
+ """Two tokens generated at different timestamps differ."""
54
+ secret = "secret"
55
+ timestamps = iter([1700000000, 1700000001])
56
+
57
+ class _CountingDatetime(datetime):
58
+ @classmethod
59
+ def now(cls, tz=None):
60
+ return datetime.fromtimestamp(next(timestamps), tz=tz)
61
+
62
+ monkeypatch.setattr("sendbee_api.auth.datetime", _CountingDatetime)
63
+
64
+ token_a = SendbeeAuth(secret).get_auth_token()
65
+ token_b = SendbeeAuth(secret).get_auth_token()
66
+
67
+ assert token_a != token_b
68
+
69
+
70
+ def test_check_auth_token_returns_true_for_self_generated_token():
71
+ """A token produced by get_auth_token verifies under the same secret."""
72
+ auth = SendbeeAuth("secret")
73
+
74
+ token = auth.get_auth_token()
75
+
76
+ assert auth.check_auth_token(token) is True
77
+
78
+
79
+ def test_check_auth_token_returns_false_when_hmac_tampered():
80
+ """A token whose HMAC has been altered fails verification."""
81
+ auth = SendbeeAuth("secret")
82
+ token = auth.get_auth_token()
83
+ decoded = base64.b64decode(token).decode("utf-8")
84
+ timestamp, hex_digest = decoded.split(".")
85
+ tampered_hex = ("0" if hex_digest[0] != "0" else "1") + hex_digest[1:]
86
+ tampered = base64.b64encode(
87
+ f"{timestamp}.{tampered_hex}".encode("utf-8")
88
+ ).decode("utf-8")
89
+
90
+ assert auth.check_auth_token(tampered) is False
91
+
92
+
93
+ def test_check_auth_token_returns_false_when_secret_differs():
94
+ """A token signed with secret A cannot be verified with secret B."""
95
+ token = SendbeeAuth("secret-a").get_auth_token()
96
+ other = SendbeeAuth("secret-b")
97
+
98
+ assert other.check_auth_token(token) is False
99
+
100
+
101
+ def test_constructor_accepts_str_and_bytes_keys():
102
+ """Both str and bytes private keys produce identical tokens."""
103
+ fixed_ts = b"1700000000"
104
+ expected = hmac.new(b"secret", fixed_ts, hashlib.sha256).hexdigest()
105
+
106
+ str_auth = SendbeeAuth("secret")
107
+ bytes_auth = SendbeeAuth(b"secret")
108
+
109
+ assert str_auth._get_encrypted_key(fixed_ts) == expected
110
+ assert bytes_auth._get_encrypted_key(fixed_ts) == expected