hyperpocket 0.0.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. hyperpocket/__init__.py +7 -0
  2. hyperpocket/auth/README.KR.md +309 -0
  3. hyperpocket/auth/README.md +323 -0
  4. hyperpocket/auth/__init__.py +24 -0
  5. hyperpocket/auth/calendly/__init__.py +0 -0
  6. hyperpocket/auth/calendly/context.py +13 -0
  7. hyperpocket/auth/calendly/oauth2_context.py +25 -0
  8. hyperpocket/auth/calendly/oauth2_handler.py +146 -0
  9. hyperpocket/auth/calendly/oauth2_schema.py +16 -0
  10. hyperpocket/auth/context.py +38 -0
  11. hyperpocket/auth/github/__init__.py +0 -0
  12. hyperpocket/auth/github/context.py +13 -0
  13. hyperpocket/auth/github/oauth2_context.py +25 -0
  14. hyperpocket/auth/github/oauth2_handler.py +143 -0
  15. hyperpocket/auth/github/oauth2_schema.py +16 -0
  16. hyperpocket/auth/github/token_context.py +12 -0
  17. hyperpocket/auth/github/token_handler.py +79 -0
  18. hyperpocket/auth/github/token_schema.py +9 -0
  19. hyperpocket/auth/google/__init__.py +0 -0
  20. hyperpocket/auth/google/context.py +15 -0
  21. hyperpocket/auth/google/oauth2_context.py +31 -0
  22. hyperpocket/auth/google/oauth2_handler.py +137 -0
  23. hyperpocket/auth/google/oauth2_schema.py +18 -0
  24. hyperpocket/auth/handler.py +171 -0
  25. hyperpocket/auth/linear/__init__.py +0 -0
  26. hyperpocket/auth/linear/context.py +15 -0
  27. hyperpocket/auth/linear/token_context.py +15 -0
  28. hyperpocket/auth/linear/token_handler.py +68 -0
  29. hyperpocket/auth/linear/token_schema.py +9 -0
  30. hyperpocket/auth/provider.py +16 -0
  31. hyperpocket/auth/schema.py +19 -0
  32. hyperpocket/auth/slack/__init__.py +0 -0
  33. hyperpocket/auth/slack/context.py +15 -0
  34. hyperpocket/auth/slack/oauth2_context.py +40 -0
  35. hyperpocket/auth/slack/oauth2_handler.py +151 -0
  36. hyperpocket/auth/slack/oauth2_schema.py +40 -0
  37. hyperpocket/auth/slack/tests/__init__.py +0 -0
  38. hyperpocket/auth/slack/tests/test_oauth2_handler.py +32 -0
  39. hyperpocket/auth/slack/tests/test_token_handler.py +23 -0
  40. hyperpocket/auth/slack/token_context.py +14 -0
  41. hyperpocket/auth/slack/token_handler.py +64 -0
  42. hyperpocket/auth/slack/token_schema.py +9 -0
  43. hyperpocket/auth/tests/__init__.py +0 -0
  44. hyperpocket/auth/tests/test_google_oauth2_handler.py +147 -0
  45. hyperpocket/auth/tests/test_slack_oauth2_handler.py +147 -0
  46. hyperpocket/auth/tests/test_slack_token_handler.py +66 -0
  47. hyperpocket/cli/__init__.py +0 -0
  48. hyperpocket/cli/__main__.py +12 -0
  49. hyperpocket/cli/pull.py +18 -0
  50. hyperpocket/cli/sync.py +17 -0
  51. hyperpocket/config/__init__.py +9 -0
  52. hyperpocket/config/auth.py +36 -0
  53. hyperpocket/config/git.py +17 -0
  54. hyperpocket/config/logger.py +81 -0
  55. hyperpocket/config/session.py +35 -0
  56. hyperpocket/config/settings.py +62 -0
  57. hyperpocket/constants.py +0 -0
  58. hyperpocket/curated_tools.py +10 -0
  59. hyperpocket/external/__init__.py +7 -0
  60. hyperpocket/external/github_client.py +19 -0
  61. hyperpocket/futures/__init__.py +7 -0
  62. hyperpocket/futures/futurestore.py +48 -0
  63. hyperpocket/pocket_auth.py +344 -0
  64. hyperpocket/pocket_main.py +351 -0
  65. hyperpocket/prompts.py +15 -0
  66. hyperpocket/repository/__init__.py +5 -0
  67. hyperpocket/repository/lock.py +156 -0
  68. hyperpocket/repository/lockfile.py +56 -0
  69. hyperpocket/repository/repository.py +18 -0
  70. hyperpocket/server/__init__.py +3 -0
  71. hyperpocket/server/auth/__init__.py +15 -0
  72. hyperpocket/server/auth/calendly.py +16 -0
  73. hyperpocket/server/auth/github.py +25 -0
  74. hyperpocket/server/auth/google.py +16 -0
  75. hyperpocket/server/auth/linear.py +18 -0
  76. hyperpocket/server/auth/slack.py +28 -0
  77. hyperpocket/server/auth/token.py +51 -0
  78. hyperpocket/server/proxy.py +63 -0
  79. hyperpocket/server/server.py +178 -0
  80. hyperpocket/server/tool/__init__.py +10 -0
  81. hyperpocket/server/tool/dto/__init__.py +0 -0
  82. hyperpocket/server/tool/dto/script.py +15 -0
  83. hyperpocket/server/tool/wasm.py +31 -0
  84. hyperpocket/session/README.KR.md +62 -0
  85. hyperpocket/session/README.md +61 -0
  86. hyperpocket/session/__init__.py +4 -0
  87. hyperpocket/session/in_memory.py +76 -0
  88. hyperpocket/session/interface.py +118 -0
  89. hyperpocket/session/redis.py +126 -0
  90. hyperpocket/session/tests/__init__.py +0 -0
  91. hyperpocket/session/tests/test_in_memory.py +145 -0
  92. hyperpocket/session/tests/test_redis.py +151 -0
  93. hyperpocket/tests/__init__.py +0 -0
  94. hyperpocket/tests/test_pocket.py +118 -0
  95. hyperpocket/tests/test_pocket_auth.py +982 -0
  96. hyperpocket/tool/README.KR.md +68 -0
  97. hyperpocket/tool/README.md +75 -0
  98. hyperpocket/tool/__init__.py +13 -0
  99. hyperpocket/tool/builtins/__init__.py +0 -0
  100. hyperpocket/tool/builtins/example/__init__.py +0 -0
  101. hyperpocket/tool/builtins/example/add_tool.py +18 -0
  102. hyperpocket/tool/function/README.KR.md +159 -0
  103. hyperpocket/tool/function/README.md +169 -0
  104. hyperpocket/tool/function/__init__.py +9 -0
  105. hyperpocket/tool/function/annotation.py +30 -0
  106. hyperpocket/tool/function/tool.py +87 -0
  107. hyperpocket/tool/tests/__init__.py +0 -0
  108. hyperpocket/tool/tests/test_function_tool.py +266 -0
  109. hyperpocket/tool/tool.py +106 -0
  110. hyperpocket/tool/wasm/README.KR.md +144 -0
  111. hyperpocket/tool/wasm/README.md +144 -0
  112. hyperpocket/tool/wasm/__init__.py +3 -0
  113. hyperpocket/tool/wasm/browser.py +63 -0
  114. hyperpocket/tool/wasm/invoker.py +41 -0
  115. hyperpocket/tool/wasm/script.py +82 -0
  116. hyperpocket/tool/wasm/templates/__init__.py +28 -0
  117. hyperpocket/tool/wasm/templates/node.py +87 -0
  118. hyperpocket/tool/wasm/templates/python.py +75 -0
  119. hyperpocket/tool/wasm/tool.py +147 -0
  120. hyperpocket/util/__init__.py +1 -0
  121. hyperpocket/util/extract_func_param_desc_from_docstring.py +97 -0
  122. hyperpocket/util/find_all_leaf_class_in_package.py +17 -0
  123. hyperpocket/util/find_all_subclass_in_package.py +29 -0
  124. hyperpocket/util/flatten_json_schema.py +45 -0
  125. hyperpocket/util/function_to_model.py +46 -0
  126. hyperpocket/util/get_objects_from_subpackage.py +28 -0
  127. hyperpocket/util/json_schema_to_model.py +69 -0
  128. hyperpocket-0.0.1.dist-info/METADATA +304 -0
  129. hyperpocket-0.0.1.dist-info/RECORD +131 -0
  130. hyperpocket-0.0.1.dist-info/WHEEL +4 -0
  131. hyperpocket-0.0.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,982 @@
1
+ import uuid
2
+ from datetime import datetime, timezone, timedelta
3
+ from unittest.async_case import IsolatedAsyncioTestCase
4
+ from unittest.mock import patch
5
+
6
+ import httpx
7
+
8
+ from hyperpocket.auth import AuthProvider, GoogleOAuth2AuthContext, SlackOAuth2AuthContext
9
+ from hyperpocket.auth.google.oauth2_handler import GoogleOAuth2AuthHandler
10
+ from hyperpocket.auth.google.oauth2_schema import GoogleOAuth2Request
11
+ from hyperpocket.config import config
12
+ from hyperpocket.config.auth import GoogleAuthConfig
13
+ from hyperpocket.config.session import SessionConfigInMemory
14
+ from hyperpocket.futures import FutureStore
15
+ from hyperpocket.pocket_auth import PocketAuth, AuthState
16
+ from hyperpocket.session.in_memory import InMemorySessionStorage
17
+
18
+
19
+ class TestPocketAuth(IsolatedAsyncioTestCase):
20
+
21
+ async def asyncSetUp(self):
22
+ self.pocket_auth = PocketAuth(
23
+ handlers=[GoogleOAuth2AuthHandler],
24
+ session_storage=InMemorySessionStorage(SessionConfigInMemory())
25
+ )
26
+
27
+ config.auth.google = GoogleAuthConfig(
28
+ client_id="test-client-id",
29
+ client_secret="test-client-secret",
30
+ )
31
+
32
+ self.thread_id = "default-thread-id"
33
+ self.profile = "default-profile"
34
+ self.auth_provider = AuthProvider.GOOGLE
35
+ self.auth_handler_name = None
36
+ self.scope = ["scope-1", "scope-2"]
37
+ return
38
+
39
+ async def test_make_request(self):
40
+ request: GoogleOAuth2Request = self.pocket_auth.make_request(
41
+ auth_scopes=self.scope,
42
+ auth_provider=self.auth_provider,
43
+ )
44
+
45
+ self.assertIsInstance(request, GoogleOAuth2Request)
46
+ self.assertEqual(request.auth_scopes, self.scope)
47
+
48
+ async def test_create_pending_session(self):
49
+ # given
50
+ future_uid = str(uuid.uuid4())
51
+ handler = self.pocket_auth.find_handler_instance(name=self.auth_handler_name, auth_provider=self.auth_provider)
52
+
53
+ # when
54
+ prev_context = self.pocket_auth.get_auth_context(
55
+ AuthProvider.GOOGLE,
56
+ thread_id=self.thread_id,
57
+ profile=self.profile,
58
+ )
59
+
60
+ session = self.pocket_auth._upsert_pending_session(
61
+ auth_handler=handler,
62
+ future_uid=future_uid,
63
+ profile=self.profile,
64
+ thread_id=self.thread_id,
65
+ scope=set(self.scope)
66
+ )
67
+
68
+ after_context = self.pocket_auth.get_auth_context(
69
+ AuthProvider.GOOGLE,
70
+ thread_id=self.thread_id,
71
+ profile=self.profile,
72
+ )
73
+
74
+ # then
75
+ self.assertIsNone(prev_context)
76
+ self.assertIsNone(after_context) # should be none even after creating session in pending session.
77
+ self.assertIsNotNone(session)
78
+ self.assertIsNone(session.auth_context)
79
+ self.assertIsNotNone(session.auth_resolve_uid)
80
+ self.assertEqual(session.auth_resolve_uid, future_uid)
81
+ self.assertEqual(session.auth_provider_name, self.auth_provider.name)
82
+ self.assertEqual(session.scoped, handler.scoped)
83
+
84
+ async def test_set_session_active(self):
85
+ # given
86
+ future_uid = str(uuid.uuid4())
87
+ handler = self.pocket_auth.find_handler_instance(name=self.auth_handler_name, auth_provider=self.auth_provider)
88
+
89
+ # when
90
+ before_session_pending = self.pocket_auth.get_auth_context(
91
+ AuthProvider.GOOGLE,
92
+ thread_id=self.thread_id,
93
+ profile=self.profile,
94
+ )
95
+
96
+ session = self.pocket_auth._upsert_pending_session(
97
+ auth_handler=handler,
98
+ future_uid=future_uid,
99
+ profile=self.profile,
100
+ thread_id=self.thread_id,
101
+ scope=set(self.scope)
102
+ )
103
+
104
+ after_session_pending = self.pocket_auth.get_auth_context(
105
+ AuthProvider.GOOGLE,
106
+ thread_id=self.thread_id,
107
+ profile=self.profile,
108
+ )
109
+
110
+ await self.pocket_auth._set_session_active(
111
+ context=GoogleOAuth2AuthContext(
112
+ access_token="access-token",
113
+ refresh_token="refresh-token",
114
+ description="test-description",
115
+ expires_at=datetime.now(tz=timezone.utc)
116
+ ),
117
+ provider=self.auth_provider,
118
+ profile=self.profile,
119
+ thread_id=self.thread_id,
120
+ )
121
+
122
+ after_session_active: GoogleOAuth2AuthContext = self.pocket_auth.get_auth_context(
123
+ AuthProvider.GOOGLE,
124
+ thread_id=self.thread_id,
125
+ profile=self.profile,
126
+ )
127
+
128
+ # then
129
+ self.assertIsNone(before_session_pending)
130
+
131
+ self.assertIsNotNone(session)
132
+ self.assertIsNone(session.auth_context) # should be none even after creating session in pending session.
133
+ self.assertIsNotNone(session.auth_resolve_uid)
134
+ self.assertEqual(session.auth_resolve_uid, future_uid)
135
+
136
+ # pending session's auth context is also none.
137
+ self.assertIsNone(after_session_pending)
138
+
139
+ # auth_context should not be none after activating session
140
+ self.assertIsNotNone(after_session_active)
141
+ self.assertEqual(after_session_active.access_token, "access-token")
142
+ self.assertEqual(after_session_active.refresh_token, "refresh-token")
143
+ self.assertEqual(after_session_active.description, "test-description")
144
+
145
+ async def test_auth_check_no_session_case(self):
146
+ # given
147
+ auth_req: GoogleOAuth2Request = self.pocket_auth.make_request(
148
+ auth_scopes=self.scope,
149
+ auth_provider=self.auth_provider,
150
+ )
151
+
152
+ # when
153
+ auth_state = self.pocket_auth.check(
154
+ auth_req=auth_req, auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
155
+
156
+ # then
157
+ self.assertEqual(auth_state, AuthState.NO_SESSION)
158
+
159
+ async def test_auth_check_pending_resolve_case(self):
160
+ # given
161
+ future_uid = str(uuid.uuid4())
162
+ handler = self.pocket_auth.find_handler_instance(name=self.auth_handler_name, auth_provider=self.auth_provider)
163
+ auth_req: GoogleOAuth2Request = self.pocket_auth.make_request(
164
+ auth_scopes=self.scope,
165
+ auth_provider=self.auth_provider,
166
+ )
167
+
168
+ # when
169
+ self.pocket_auth._upsert_pending_session(
170
+ auth_handler=handler,
171
+ future_uid=future_uid,
172
+ profile=self.profile,
173
+ thread_id=self.thread_id,
174
+ scope=set(self.scope)
175
+ )
176
+
177
+ auth_state = self.pocket_auth.check(
178
+ auth_req=auth_req, auth_provider=self.auth_provider, auth_handler_name=handler.name,
179
+ thread_id=self.thread_id, profile=self.profile)
180
+
181
+ # then
182
+ self.assertEqual(auth_state, AuthState.PENDING_RESOLVE)
183
+
184
+ async def test_auth_check_resolved_case(self):
185
+ """
186
+ Test auth state will be changed to resolved in case that the future is resolved.
187
+ """
188
+ # given
189
+ future_uid = str(uuid.uuid4())
190
+ handler = self.pocket_auth.find_handler_instance(name=self.auth_handler_name, auth_provider=self.auth_provider)
191
+ auth_req: GoogleOAuth2Request = self.pocket_auth.make_request(
192
+ auth_scopes=self.scope,
193
+ auth_provider=self.auth_provider,
194
+ )
195
+
196
+ # when
197
+ future_data = FutureStore.create_future(uid=future_uid)
198
+ self.pocket_auth._upsert_pending_session(
199
+ auth_handler=handler,
200
+ future_uid=future_uid,
201
+ profile=self.profile,
202
+ thread_id=self.thread_id,
203
+ scope=set(self.scope)
204
+ )
205
+
206
+ future_data.future.set_result("test-code")
207
+ auth_state = self.pocket_auth.check(
208
+ auth_req=auth_req, auth_provider=self.auth_provider, auth_handler_name=handler.name,
209
+ thread_id=self.thread_id, profile=self.profile)
210
+
211
+ # then
212
+ self.assertEqual(auth_state, AuthState.RESOLVED)
213
+
214
+ async def test_auth_check_skip_auth_case(self):
215
+ """
216
+ Test in case that the pending session get active, auth context is set.
217
+ And auth state should be SKIP_AUTH.
218
+ """
219
+
220
+ # given
221
+ future_uid = str(uuid.uuid4())
222
+ handler = self.pocket_auth.find_handler_instance(name=self.auth_handler_name, auth_provider=self.auth_provider)
223
+ auth_req: GoogleOAuth2Request = self.pocket_auth.make_request(
224
+ auth_scopes=self.scope,
225
+ auth_provider=self.auth_provider,
226
+ )
227
+
228
+ # when
229
+ # set pending session
230
+ self.pocket_auth._upsert_pending_session(
231
+ auth_handler=handler,
232
+ future_uid=future_uid,
233
+ profile=self.profile,
234
+ thread_id=self.thread_id,
235
+ scope=set(self.scope)
236
+ )
237
+
238
+ # activate session
239
+ await self.pocket_auth._set_session_active(
240
+ context=GoogleOAuth2AuthContext(
241
+ access_token="access-token",
242
+ refresh_token="refresh-token",
243
+ description="test-description",
244
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(minutes=30),
245
+ ),
246
+ provider=self.auth_provider,
247
+ profile=self.profile,
248
+ thread_id=self.thread_id,
249
+ )
250
+
251
+ auth_state = self.pocket_auth.check(
252
+ auth_req=auth_req, auth_provider=self.auth_provider, auth_handler_name=handler.name,
253
+ thread_id=self.thread_id, profile=self.profile
254
+ )
255
+
256
+ # then
257
+ self.assertEqual(auth_state, AuthState.SKIP_AUTH)
258
+
259
+ async def test_auth_check_skip_auth_case_superset_scope(self):
260
+ """
261
+ Test in case that auth request scope is subset of existing session scope,
262
+ Auth state should be SKIP_AUTH.
263
+ """
264
+ # given
265
+ future_uid = str(uuid.uuid4())
266
+ handler = self.pocket_auth.find_handler_instance(name=self.auth_handler_name,
267
+ auth_provider=self.auth_provider)
268
+ auth_req = self.pocket_auth.make_request(
269
+ auth_scopes=self.scope,
270
+ auth_provider=self.auth_provider,
271
+ )
272
+ updated_auth_req = self.pocket_auth.make_request(
273
+ auth_scopes=["scope-1"],
274
+ auth_provider=self.auth_provider,
275
+ )
276
+
277
+ # when
278
+ # set pending session
279
+ self.pocket_auth._upsert_pending_session(
280
+ auth_handler=handler,
281
+ future_uid=future_uid,
282
+ profile=self.profile,
283
+ thread_id=self.thread_id,
284
+ scope=set(self.scope)
285
+ )
286
+
287
+ # activate session
288
+ await self.pocket_auth._set_session_active(
289
+ context=SlackOAuth2AuthContext(
290
+ access_token="access-token",
291
+ refresh_token="refresh-token",
292
+ description="test-description",
293
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(minutes=30),
294
+ ),
295
+ provider=self.auth_provider,
296
+ profile=self.profile,
297
+ thread_id=self.thread_id,
298
+ )
299
+
300
+ auth_state = self.pocket_auth.check(auth_req=auth_req, auth_provider=self.auth_provider,
301
+ thread_id=self.thread_id, profile=self.profile)
302
+ updated_auth_state = self.pocket_auth.check(auth_req=updated_auth_req, auth_provider=self.auth_provider,
303
+ thread_id=self.thread_id, profile=self.profile)
304
+
305
+ # then
306
+ self.assertEqual(auth_state, AuthState.SKIP_AUTH)
307
+ self.assertEqual(updated_auth_state, AuthState.SKIP_AUTH)
308
+
309
+ async def test_auth_check_skip_auth_case_non_scoped_handler(self):
310
+ """
311
+ Test in case that the session's handler is non-scoped,
312
+ The auth state Should be SKIP_AUTH even the checking scope is not a subset of existing session scope.
313
+ """
314
+ # given
315
+ auth_handler_name = 'slack-token' # temporally use slack provider/handler for testing
316
+ auth_provider = AuthProvider.SLACK
317
+ future_uid = str(uuid.uuid4())
318
+ handler = self.pocket_auth.find_handler_instance(name=auth_handler_name,
319
+ auth_provider=auth_provider)
320
+ auth_req = self.pocket_auth.make_request(
321
+ auth_scopes=self.scope,
322
+ auth_provider=auth_provider,
323
+ auth_handler_name=auth_handler_name,
324
+ )
325
+ updated_auth_req = self.pocket_auth.make_request(
326
+ auth_scopes=self.scope + ["new_scope"],
327
+ auth_provider=auth_provider,
328
+ auth_handler_name=auth_handler_name,
329
+ )
330
+
331
+ # when
332
+ # set pending session
333
+ self.pocket_auth._upsert_pending_session(
334
+ auth_handler=handler,
335
+ future_uid=future_uid,
336
+ profile=self.profile,
337
+ thread_id=self.thread_id,
338
+ scope=set(self.scope)
339
+ )
340
+
341
+ # activate session
342
+ await self.pocket_auth._set_session_active(
343
+ context=SlackOAuth2AuthContext(
344
+ access_token="access-token",
345
+ refresh_token="refresh-token",
346
+ description="test-description",
347
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(minutes=30),
348
+ ),
349
+ provider=auth_provider,
350
+ profile=self.profile,
351
+ thread_id=self.thread_id,
352
+ )
353
+
354
+ auth_state = self.pocket_auth.check(auth_req=auth_req, auth_provider=auth_provider,
355
+ thread_id=self.thread_id, profile=self.profile)
356
+ updated_auth_state = self.pocket_auth.check(auth_req=updated_auth_req, auth_provider=auth_provider,
357
+ thread_id=self.thread_id, profile=self.profile)
358
+
359
+ # then
360
+ self.assertEqual(auth_state, AuthState.SKIP_AUTH)
361
+ self.assertEqual(updated_auth_state, AuthState.SKIP_AUTH)
362
+
363
+ async def test_auth_check_by_other_provider(self):
364
+ """
365
+ Test in case of checking session by other provider, the auth state Should be NO_SESSION
366
+ """
367
+
368
+ # given
369
+ future_uid = str(uuid.uuid4())
370
+ handler = self.pocket_auth.find_handler_instance(name=self.auth_handler_name,
371
+ auth_provider=self.auth_provider)
372
+ auth_req = self.pocket_auth.make_request(
373
+ auth_scopes=self.scope,
374
+ auth_provider=self.auth_provider,
375
+ )
376
+
377
+ # when
378
+ # set pending session
379
+ self.pocket_auth._upsert_pending_session(
380
+ auth_handler=handler,
381
+ future_uid=future_uid,
382
+ profile=self.profile,
383
+ thread_id=self.thread_id,
384
+ scope=set(self.scope)
385
+ )
386
+
387
+ # activate session
388
+ await self.pocket_auth._set_session_active(
389
+ context=SlackOAuth2AuthContext(
390
+ access_token="access-token",
391
+ refresh_token="refresh-token",
392
+ description="test-description",
393
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(minutes=30),
394
+ ),
395
+ provider=self.auth_provider,
396
+ profile=self.profile,
397
+ thread_id=self.thread_id,
398
+ )
399
+
400
+ # check by other auth provider(SLACK)
401
+ auth_state = self.pocket_auth.check(
402
+ auth_req=auth_req, auth_provider=AuthProvider.SLACK,
403
+ thread_id=self.thread_id, profile=self.profile
404
+ )
405
+
406
+ # then
407
+ self.assertEqual(auth_state, AuthState.NO_SESSION)
408
+
409
+ async def test_auth_check_do_auth_case_new_scopes(self):
410
+ """
411
+ Test in case of checking session by new scopes, and this is not a subset of existing scopes
412
+ The auth state Should be DO_AUTH
413
+ """
414
+ # given
415
+ future_uid = str(uuid.uuid4())
416
+ handler = self.pocket_auth.find_handler_instance(name=self.auth_handler_name,
417
+ auth_provider=self.auth_provider)
418
+ auth_req = self.pocket_auth.make_request(
419
+ auth_scopes=self.scope,
420
+ auth_provider=self.auth_provider,
421
+ )
422
+ updated_auth_req = self.pocket_auth.make_request(
423
+ auth_scopes=self.scope + ["new_scope"],
424
+ auth_provider=self.auth_provider,
425
+ )
426
+
427
+ # when
428
+ # set pending session
429
+ self.pocket_auth._upsert_pending_session(
430
+ auth_handler=handler,
431
+ future_uid=future_uid,
432
+ profile=self.profile,
433
+ thread_id=self.thread_id,
434
+ scope=set(self.scope)
435
+ )
436
+
437
+ # activate session
438
+ await self.pocket_auth._set_session_active(
439
+ context=SlackOAuth2AuthContext(
440
+ access_token="access-token",
441
+ refresh_token="refresh-token",
442
+ description="test-description",
443
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(minutes=30),
444
+ ),
445
+ provider=self.auth_provider,
446
+ profile=self.profile,
447
+ thread_id=self.thread_id,
448
+ )
449
+
450
+ auth_state = self.pocket_auth.check(auth_req=auth_req, auth_provider=self.auth_provider,
451
+ thread_id=self.thread_id, profile=self.profile)
452
+ updated_auth_state = self.pocket_auth.check(auth_req=updated_auth_req, auth_provider=self.auth_provider,
453
+ thread_id=self.thread_id, profile=self.profile)
454
+
455
+ # then
456
+ self.assertEqual(auth_state, AuthState.SKIP_AUTH)
457
+ self.assertEqual(updated_auth_state, AuthState.DO_AUTH)
458
+
459
+ async def test_auth_check_do_refresh(self):
460
+ """
461
+ Test if checking near expired session
462
+ The auth state Should be DO_REFRESH
463
+ """
464
+ # given
465
+ future_uid = str(uuid.uuid4())
466
+ handler = self.pocket_auth.find_handler_instance(name=self.auth_handler_name,
467
+ auth_provider=self.auth_provider)
468
+ auth_req = self.pocket_auth.make_request(
469
+ auth_scopes=self.scope,
470
+ auth_provider=self.auth_provider,
471
+ )
472
+
473
+ # when
474
+ # set pending session
475
+ self.pocket_auth._upsert_pending_session(
476
+ auth_handler=handler,
477
+ future_uid=future_uid,
478
+ profile=self.profile,
479
+ thread_id=self.thread_id,
480
+ scope=set(self.scope)
481
+ )
482
+
483
+ # activate session
484
+ await self.pocket_auth._set_session_active(
485
+ context=SlackOAuth2AuthContext(
486
+ access_token="access-token",
487
+ refresh_token="refresh-token",
488
+ description="test-description",
489
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(minutes=5), # near expirations
490
+ ),
491
+ provider=self.auth_provider,
492
+ profile=self.profile,
493
+ thread_id=self.thread_id,
494
+ )
495
+
496
+ auth_state = self.pocket_auth.check(auth_req=auth_req, auth_provider=self.auth_provider,
497
+ thread_id=self.thread_id, profile=self.profile)
498
+
499
+ # then
500
+ self.assertEqual(auth_state, AuthState.DO_REFRESH)
501
+
502
+ ###################################################################################################################
503
+ # Integration Test
504
+ ###################################################################################################################
505
+ async def test_prepare_no_session_case(self):
506
+ # given
507
+ auth_req = self.pocket_auth.make_request(
508
+ auth_scopes=self.scope,
509
+ auth_provider=self.auth_provider,
510
+ )
511
+
512
+ # when
513
+ prepared_url = self.pocket_auth.prepare(
514
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
515
+ auth_provider=self.auth_provider,
516
+ auth_handler_name=self.auth_handler_name)
517
+
518
+ session = self.pocket_auth.session_storage.get(
519
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
520
+
521
+ # then
522
+ self.assertIsNotNone(prepared_url)
523
+ self.assertIsNotNone(session)
524
+ self.assertIsNotNone(session.auth_resolve_uid) # its currently pending session
525
+
526
+ async def test_prepare_do_auth_new_scopes_case(self):
527
+ """
528
+ Test in case that previous session is already active but new request needs new scopes.
529
+ It creates another new session, so the future uid is also different.
530
+ But if the previous session state is PENDING_RESOLVE, it doesn't create new session. so the future uid is same as before.
531
+ """
532
+ # given
533
+ new_scopes = self.scope + ["new-scope"]
534
+ auth_req = self.pocket_auth.make_request(
535
+ auth_scopes=self.scope,
536
+ auth_provider=self.auth_provider,
537
+ )
538
+
539
+ new_scope_auth_req = self.pocket_auth.make_request(
540
+ auth_scopes=new_scopes,
541
+ auth_provider=self.auth_provider,
542
+ )
543
+
544
+ # when
545
+ prepared_url = self.pocket_auth.prepare(
546
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
547
+ auth_provider=self.auth_provider,
548
+ auth_handler_name=self.auth_handler_name)
549
+
550
+ session = self.pocket_auth.session_storage.get(
551
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
552
+
553
+ # make session active
554
+ future_data = FutureStore.get_future(session.auth_resolve_uid)
555
+ future_data.future.set_result("test-code")
556
+ await self.pocket_auth._set_session_active(
557
+ context=GoogleOAuth2AuthContext(
558
+ access_token="access-token",
559
+ refresh_token="refresh-token",
560
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(minutes=30),
561
+ description="test-description",
562
+ ),
563
+ provider=self.auth_provider,
564
+ profile=self.profile,
565
+ thread_id=self.thread_id,
566
+ )
567
+
568
+ new_scope_prepared_url = self.pocket_auth.prepare(
569
+ auth_req=new_scope_auth_req,
570
+ profile=self.profile,
571
+ thread_id=self.thread_id,
572
+ auth_provider=self.auth_provider,
573
+ auth_handler_name=self.auth_handler_name,
574
+ )
575
+ new_scope_session = self.pocket_auth.session_storage.get(
576
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
577
+
578
+ # then
579
+ self.assertNotEqual(prepared_url, new_scope_prepared_url)
580
+ self.assertIsNotNone(new_scope_session.auth_resolve_uid)
581
+ # future uid is different, because it makes new session
582
+ self.assertNotEqual(new_scope_session.auth_resolve_uid, session.auth_resolve_uid)
583
+
584
+ async def test_prepare_pending_resolve_case(self):
585
+ """
586
+ Test in case that the session is PENDING_RESOLVE state,
587
+ It should return previous session's preparing url
588
+ """
589
+ # given
590
+ auth_req = self.pocket_auth.make_request(
591
+ auth_scopes=self.scope,
592
+ auth_provider=self.auth_provider,
593
+ )
594
+
595
+ # when
596
+ first_prepared_url = self.pocket_auth.prepare(
597
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
598
+ auth_provider=self.auth_provider,
599
+ auth_handler_name=self.auth_handler_name)
600
+ first_session = self.pocket_auth.session_storage.get(
601
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
602
+ first_future_uid = first_session.auth_resolve_uid
603
+
604
+ second_prepared_url = self.pocket_auth.prepare(
605
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
606
+ auth_provider=self.auth_provider,
607
+ auth_handler_name=self.auth_handler_name)
608
+ second_session = self.pocket_auth.session_storage.get(
609
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
610
+ second_future_uid = second_session.auth_resolve_uid
611
+
612
+ # then
613
+ self.assertIsNotNone(first_prepared_url)
614
+ self.assertIsNotNone(second_prepared_url)
615
+ self.assertEqual(first_prepared_url, second_prepared_url)
616
+
617
+ self.assertIsNotNone(first_session)
618
+ self.assertIsNotNone(second_session)
619
+
620
+ self.assertEqual(first_future_uid, second_future_uid)
621
+
622
+ async def test_prepare_pending_resolve_new_scopes_case(self):
623
+ """
624
+ Test in case that the session is PENDING_RESOLVE state, but this session needs new scopes
625
+ It should return new authentication uri including new scopes.
626
+ But it will return same future uid as before.
627
+ """
628
+ # given
629
+ auth_req = self.pocket_auth.make_request(
630
+ auth_scopes=self.scope,
631
+ auth_provider=self.auth_provider,
632
+ )
633
+
634
+ new_scope = self.scope + ["new-scope"]
635
+ new_scope_auth_req = self.pocket_auth.make_request(
636
+ auth_scopes=new_scope,
637
+ auth_provider=self.auth_provider,
638
+ )
639
+
640
+ # when
641
+ prepared_url = self.pocket_auth.prepare(
642
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
643
+ auth_provider=self.auth_provider,
644
+ auth_handler_name=self.auth_handler_name)
645
+ session = self.pocket_auth.session_storage.get(
646
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
647
+ future_uid = session.auth_resolve_uid
648
+
649
+ new_scope_prepared_url = self.pocket_auth.prepare(
650
+ auth_req=new_scope_auth_req, profile=self.profile, thread_id=self.thread_id,
651
+ auth_provider=self.auth_provider,
652
+ auth_handler_name=self.auth_handler_name)
653
+ new_scope_session = self.pocket_auth.session_storage.get(
654
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
655
+ new_scope_future_uid = new_scope_session.auth_resolve_uid
656
+
657
+ # then
658
+ self.assertIsNotNone(prepared_url)
659
+ self.assertIsNotNone(new_scope_prepared_url)
660
+ self.assertNotEqual(
661
+ prepared_url, new_scope_prepared_url) # it should be different because url includes auth scopes information
662
+
663
+ self.assertIsNotNone(session)
664
+ self.assertIsNotNone(new_scope_session)
665
+
666
+ self.assertEqual(future_uid, new_scope_future_uid) # it will be same
667
+ self.assertEqual(new_scope_session.auth_scopes, set(new_scope))
668
+
669
+ async def test_prepare_resolved_case(self):
670
+ """
671
+ Test in case that the session is RESOLVED state.
672
+ `prepare` method don't handle this case. so it just return None.
673
+ """
674
+ # given
675
+ auth_req = self.pocket_auth.make_request(
676
+ auth_scopes=self.scope,
677
+ auth_provider=self.auth_provider,
678
+ )
679
+
680
+ # when
681
+ # create session
682
+ prepared_url = self.pocket_auth.prepare(
683
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
684
+ auth_provider=self.auth_provider,
685
+ auth_handler_name=self.auth_handler_name)
686
+ session = self.pocket_auth.session_storage.get(
687
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
688
+
689
+ # set session resolved
690
+ future_data = FutureStore.get_future(session.auth_resolve_uid)
691
+ future_data.future.set_result("test-code")
692
+
693
+ # prepare while in resolved
694
+ resolved_prepared_url = self.pocket_auth.prepare(
695
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
696
+ auth_provider=self.auth_provider,
697
+ auth_handler_name=self.auth_handler_name)
698
+
699
+ # then
700
+ self.assertIsNotNone(prepared_url)
701
+ self.assertIsNone(resolved_prepared_url) # should be none if auth state is RESOLVED
702
+
703
+ async def test_prepare_skip_auth_case(self):
704
+ """
705
+ Test in case that the session is SKIP_AUTH state.
706
+ `prepare` method don't handle this case. so it just return None.
707
+ """
708
+ # given
709
+ auth_req = self.pocket_auth.make_request(
710
+ auth_scopes=self.scope,
711
+ auth_provider=self.auth_provider,
712
+ )
713
+
714
+ # when
715
+ # create session
716
+ prepared_url = self.pocket_auth.prepare(
717
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
718
+ auth_provider=self.auth_provider,
719
+ auth_handler_name=self.auth_handler_name)
720
+ session = self.pocket_auth.session_storage.get(
721
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
722
+
723
+ # make session active
724
+ future_data = FutureStore.get_future(session.auth_resolve_uid)
725
+ await self.pocket_auth._set_session_active(
726
+ context=GoogleOAuth2AuthContext(
727
+ access_token="access-token",
728
+ refresh_token="refresh-token",
729
+ description="test-description",
730
+ expires_at=datetime.now(tz=timezone.utc) + timedelta(minutes=60)
731
+ ),
732
+ provider=self.auth_provider,
733
+ profile=self.profile,
734
+ thread_id=self.thread_id,
735
+ )
736
+ future_data.future.set_result("test-code")
737
+
738
+ # prepare while in skip_auth
739
+ skip_auth_prepared_url = self.pocket_auth.prepare(
740
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
741
+ auth_provider=self.auth_provider,
742
+ auth_handler_name=self.auth_handler_name)
743
+
744
+ # then
745
+ self.assertIsNotNone(prepared_url)
746
+ self.assertIsNone(skip_auth_prepared_url) # should be none if auth state is RESOLVED
747
+
748
+ async def test_prepare_do_refresh_case(self):
749
+ """
750
+ Test in case that the session is DO_REFRESH state.
751
+ `prepare` method don't handle this case. so it just return None.
752
+ """
753
+ # given
754
+ auth_req = self.pocket_auth.make_request(
755
+ auth_scopes=self.scope,
756
+ auth_provider=self.auth_provider,
757
+ )
758
+
759
+ # when
760
+ # create session
761
+ prepared_url = self.pocket_auth.prepare(
762
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
763
+ auth_provider=self.auth_provider,
764
+ auth_handler_name=self.auth_handler_name)
765
+ session = self.pocket_auth.session_storage.get(
766
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
767
+
768
+ # make session active
769
+ future_data = FutureStore.get_future(session.auth_resolve_uid)
770
+ await self.pocket_auth._set_session_active(
771
+ context=GoogleOAuth2AuthContext(
772
+ access_token="access-token",
773
+ refresh_token="refresh-token",
774
+ description="test-description",
775
+ expires_at=datetime.now(tz=timezone.utc) # near expiration
776
+ ),
777
+ provider=self.auth_provider,
778
+ profile=self.profile,
779
+ thread_id=self.thread_id,
780
+ )
781
+ future_data.future.set_result("test-code")
782
+
783
+ # prepare while in skip_auth
784
+ do_refresh_prepared_url = self.pocket_auth.prepare(
785
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
786
+ auth_provider=self.auth_provider,
787
+ auth_handler_name=self.auth_handler_name)
788
+
789
+ # then
790
+ self.assertIsNotNone(prepared_url)
791
+ self.assertIsNone(do_refresh_prepared_url) # should be none if auth state is RESOLVED
792
+
793
+ async def test_delete_session(self):
794
+ # given
795
+ auth_req = self.pocket_auth.make_request(
796
+ auth_scopes=self.scope,
797
+ auth_provider=self.auth_provider,
798
+ )
799
+
800
+ # when
801
+ self.pocket_auth.prepare(
802
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
803
+ auth_provider=self.auth_provider,
804
+ auth_handler_name=self.auth_handler_name)
805
+ session_before_delete = self.pocket_auth.session_storage.get(
806
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
807
+
808
+ deleted = self.pocket_auth.delete_session(
809
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
810
+
811
+ session_after_delete = self.pocket_auth.session_storage.get(
812
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile)
813
+
814
+ # then
815
+ self.assertIsNotNone(session_before_delete)
816
+ self.assertTrue(deleted)
817
+ self.assertIsNone(session_after_delete)
818
+
819
+ async def test_authenticate_resolved_case(self):
820
+ """
821
+ Test in case that preparing is complete.
822
+ once preparing is complete, the auth state is set to `RESOLVED`
823
+ while authenticating process, it will get authentication code from future created in prepare step
824
+ """
825
+ # given
826
+ auth_req = self.pocket_auth.make_request(
827
+ auth_scopes=self.scope,
828
+ auth_provider=self.auth_provider,
829
+ auth_handler_name=self.auth_handler_name
830
+ )
831
+
832
+ mock_response = httpx.Response(
833
+ status_code=200,
834
+ json={
835
+ "access_token": "access-token",
836
+ "expires_in": 3600,
837
+ "refresh_token": "refresh-token",
838
+ "scope": ",".join(self.scope),
839
+ "token_type": "Bearer",
840
+ }
841
+ )
842
+
843
+ # when
844
+ prepared_url = self.pocket_auth.prepare(
845
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
846
+ auth_provider=self.auth_provider, auth_handler_name=self.auth_handler_name
847
+ )
848
+ session = self.pocket_auth.session_storage.get(
849
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile
850
+ )
851
+ future_data = FutureStore.get_future(session.auth_resolve_uid)
852
+ future_data.future.set_result("test-code")
853
+
854
+ with patch("httpx.AsyncClient.post", return_value=mock_response):
855
+ context: GoogleOAuth2AuthContext = await self.pocket_auth.authenticate_async(
856
+ auth_req=auth_req, auth_provider=self.auth_provider,
857
+ thread_id=self.thread_id, profile=self.profile
858
+ )
859
+
860
+ # then
861
+ self.assertIsNotNone(prepared_url)
862
+ self.assertIsNotNone(context)
863
+ self.assertEqual(context.access_token, "access-token")
864
+ self.assertEqual(context.refresh_token, "refresh-token")
865
+
866
+ async def test_authenticate_skip_auth_case(self):
867
+ """
868
+ Test in case that the session already exists,
869
+ It should return already existing session.
870
+ """
871
+ # given
872
+ auth_req = self.pocket_auth.make_request(
873
+ auth_scopes=self.scope,
874
+ auth_provider=self.auth_provider,
875
+ auth_handler_name=self.auth_handler_name
876
+ )
877
+
878
+ mock_response = httpx.Response(
879
+ status_code=200,
880
+ json={
881
+ "access_token": "access-token",
882
+ "expires_in": 3600,
883
+ "refresh_token": "refresh-token",
884
+ "scope": ",".join(self.scope),
885
+ "token_type": "Bearer",
886
+ }
887
+ )
888
+
889
+ # when
890
+ self.pocket_auth.prepare(
891
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
892
+ auth_provider=self.auth_provider, auth_handler_name=self.auth_handler_name
893
+ )
894
+ session = self.pocket_auth.session_storage.get(
895
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile
896
+ )
897
+ future_data = FutureStore.get_future(session.auth_resolve_uid)
898
+ future_data.future.set_result("test-code")
899
+
900
+ with patch("httpx.AsyncClient.post", return_value=mock_response):
901
+ first_context: GoogleOAuth2AuthContext = await self.pocket_auth.authenticate_async(
902
+ auth_req=auth_req, auth_provider=self.auth_provider,
903
+ thread_id=self.thread_id, profile=self.profile
904
+ )
905
+
906
+ # don't have to mocking response, because it just returns already existing session's context
907
+ second_context: GoogleOAuth2AuthContext = await self.pocket_auth.authenticate_async(
908
+ auth_req=auth_req, auth_provider=self.auth_provider,
909
+ thread_id=self.thread_id, profile=self.profile
910
+ )
911
+
912
+ # then
913
+ self.assertEqual(first_context.access_token, second_context.access_token)
914
+ self.assertEqual(first_context.refresh_token, second_context.refresh_token)
915
+
916
+ async def test_authenticate_do_refresh_case(self):
917
+ """
918
+ Test in case of refreshing authentication.
919
+ The outdated session should be replaced by refreshed session.
920
+ """
921
+
922
+ # given
923
+ auth_req = self.pocket_auth.make_request(
924
+ auth_scopes=self.scope,
925
+ auth_provider=self.auth_provider,
926
+ auth_handler_name=self.auth_handler_name
927
+ )
928
+
929
+ mock_response = httpx.Response(
930
+ status_code=200,
931
+ json={
932
+ "access_token": "access-token",
933
+ "expires_in": 300,
934
+ "refresh_token": "refresh-token",
935
+ "scope": ",".join(self.scope),
936
+ "token_type": "Bearer",
937
+ }
938
+ )
939
+
940
+ mock_refresh_response = httpx.Response(
941
+ status_code=200,
942
+ json={
943
+ "access_token": "new-access-token",
944
+ "expires_in": 3600,
945
+ "scope": ",".join(self.scope),
946
+ "token_type": "Bearer",
947
+ }
948
+ )
949
+
950
+ # when
951
+ self.pocket_auth.prepare(
952
+ auth_req=auth_req, profile=self.profile, thread_id=self.thread_id,
953
+ auth_provider=self.auth_provider, auth_handler_name=self.auth_handler_name
954
+ )
955
+ session = self.pocket_auth.session_storage.get(
956
+ auth_provider=self.auth_provider, thread_id=self.thread_id, profile=self.profile
957
+ )
958
+ future_data = FutureStore.get_future(session.auth_resolve_uid)
959
+ future_data.future.set_result("test-code")
960
+
961
+ # authenticate session at the first
962
+ with patch("httpx.AsyncClient.post", return_value=mock_response):
963
+ first_context: GoogleOAuth2AuthContext = await self.pocket_auth.authenticate_async(
964
+ auth_req=auth_req, auth_provider=self.auth_provider,
965
+ thread_id=self.thread_id, profile=self.profile
966
+ )
967
+
968
+ # refresh authentication
969
+ with patch("httpx.AsyncClient.post", return_value=mock_refresh_response):
970
+ second_context: GoogleOAuth2AuthContext = await self.pocket_auth.authenticate_async(
971
+ auth_req=auth_req, auth_provider=self.auth_provider,
972
+ thread_id=self.thread_id, profile=self.profile
973
+ )
974
+
975
+ first_context_time_diff = (first_context.expires_at - datetime.now(tz=timezone.utc)).total_seconds()
976
+ second_context_time_diff = (second_context.expires_at - datetime.now(tz=timezone.utc)).total_seconds()
977
+
978
+ # then
979
+ self.assertEqual(first_context.access_token, "access-token")
980
+ self.assertEqual(second_context.access_token, "new-access-token")
981
+ self.assertTrue(first_context_time_diff < 300)
982
+ self.assertTrue(second_context_time_diff > 3000)