-
-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathtest_api.py
More file actions
165 lines (139 loc) · 5.54 KB
/
Copy pathtest_api.py
File metadata and controls
165 lines (139 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import os
import unittest
from datetime import datetime, timezone
from unittest.mock import Mock, patch
import pytest
from dotenv import load_dotenv
from substack import Api
from substack.exceptions import SubstackAPIException
load_dotenv()
def _api_from_env() -> Api:
cookies_string = os.getenv("COOKIES_STRING")
cookies_path = os.getenv("COOKIES_PATH")
publication_url = os.getenv("PUBLICATION_URL")
if cookies_string or cookies_path:
return Api(
cookies_string=cookies_string,
cookies_path=cookies_path,
publication_url=publication_url,
)
return Api(
email=os.getenv("EMAIL"),
password=os.getenv("PASSWORD"),
publication_url=publication_url,
)
_e2e = unittest.skipUnless(
os.getenv("RUN_SUBSTACK_E2E"),
"set RUN_SUBSTACK_E2E=1 and configure credentials to run live API tests",
)
class ApiTest(unittest.TestCase):
def test_api_retries_rate_limited_get_and_delete_requests(self):
publication = {
"subdomain": "writer",
"publication_url": "https://writer.substack.com",
}
with (
patch("requests.Session") as session_class,
patch.object(Api, "get_user_primary_publication", return_value=publication),
patch.object(Api, "change_publication"),
):
Api(cookies_string="sid=value")
adapters = [
call.args[1] for call in session_class.return_value.mount.call_args_list
]
self.assertEqual(len(adapters), 2)
retry = adapters[0].max_retries
self.assertEqual(retry.total, 4)
self.assertEqual(retry.status, 4)
self.assertEqual(retry.status_forcelist, (429,))
self.assertEqual(retry.allowed_methods, frozenset({"GET", "DELETE"}))
self.assertFalse(retry.raise_on_status)
def test_api_exception(self):
response = Mock(status_code=401, text="Unauthorized")
with self.assertRaises(SubstackAPIException):
with patch("requests.Session.post", return_value=response):
Api(email="", password="")
def test_get_publication_subscriber_count_from_legacy_response(self):
api = Api.__new__(Api)
api.publication_url = "https://writer.substack.com/api/v1"
api._session = Mock()
response = Mock(status_code=200)
response.json.return_value = {"subscriberCount": 123}
api._session.get.return_value = response
self.assertEqual(api.get_publication_subscriber_count(), 123)
def test_get_publication_subscriber_count_from_subscribers(self):
api = Api.__new__(Api)
api.publication_url = "https://writer.substack.com/api/v1"
api._session = Mock()
response = Mock(status_code=200)
response.json.return_value = {"subscribers": [{"id": 1}, {"id": 2}]}
api._session.get.return_value = response
self.assertEqual(api.get_publication_subscriber_count(), 2)
def test_get_publication_subscriber_count_prefers_legacy_count(self):
api = Api.__new__(Api)
api.publication_url = "https://writer.substack.com/api/v1"
api._session = Mock()
response = Mock(status_code=200)
response.json.return_value = {
"subscriberCount": 123,
"subscribers": [{"id": 1}],
}
api._session.get.return_value = response
self.assertEqual(api.get_publication_subscriber_count(), 123)
def test_schedule_draft_uses_scheduled_release_contract(self):
api = Api.__new__(Api)
api.publication_url = "https://writer.substack.com/api/v1"
api._session = Mock()
response = Mock(status_code=200)
response.json.return_value = {"scheduled": True}
api._session.post.return_value = response
scheduled_at = datetime(2030, 1, 2, 3, 4, 5, tzinfo=timezone.utc)
self.assertEqual(
api.schedule_draft(42, scheduled_at),
{"scheduled": True},
)
api._session.post.assert_called_once_with(
"https://writer.substack.com/api/v1/drafts/42/scheduled_release",
json={"trigger_at": "2030-01-02T03:04:05+00:00"},
)
def test_unschedule_draft_deletes_scheduled_release(self):
api = Api.__new__(Api)
api.publication_url = "https://writer.substack.com/api/v1"
api._session = Mock()
response = Mock(status_code=200)
response.json.return_value = {"scheduled": False}
api._session.delete.return_value = response
self.assertEqual(api.unschedule_draft(42), {"scheduled": False})
api._session.delete.assert_called_once_with(
"https://writer.substack.com/api/v1/drafts/42/scheduled_release"
)
@pytest.mark.live
@_e2e
def test_get_posts(self):
api = _api_from_env()
posts = api.get_posts()
self.assertIsNotNone(posts)
@pytest.mark.live
@_e2e
def test_get_drafts(self):
api = _api_from_env()
drafts = api.get_drafts()
self.assertIsNotNone(drafts)
@pytest.mark.live
@_e2e
def test_publication_users(self):
api = _api_from_env()
users = api.get_publication_users()
self.assertIsNotNone(users)
@pytest.mark.live
@_e2e
def test_get_categories(self):
api = _api_from_env()
categories = api.get_categories()
self.assertIsNotNone(categories)
@pytest.mark.live
@_e2e
def test_get_single_category(self):
api = _api_from_env()
category = api.get_single_category(4, "all", limit=100)
self.assertIsNotNone(category)