hyperpocket 0.0.2__py3-none-any.whl → 0.1.8__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. hyperpocket/auth/README.md +3 -3
  2. hyperpocket/auth/__init__.py +0 -8
  3. hyperpocket/auth/gumloop/context.py +13 -0
  4. hyperpocket/auth/gumloop/token_context.py +15 -0
  5. hyperpocket/auth/gumloop/token_handler.py +66 -0
  6. hyperpocket/auth/gumloop/token_schema.py +8 -0
  7. hyperpocket/auth/linear/token_context.py +1 -1
  8. hyperpocket/auth/notion/README.md +28 -0
  9. hyperpocket/auth/notion/context.py +15 -0
  10. hyperpocket/auth/notion/token_context.py +14 -0
  11. hyperpocket/auth/notion/token_handler.py +65 -0
  12. hyperpocket/auth/notion/token_schema.py +10 -0
  13. hyperpocket/auth/provider.py +8 -5
  14. hyperpocket/auth/reddit/context.py +15 -0
  15. hyperpocket/auth/reddit/oauth2_context.py +32 -0
  16. hyperpocket/auth/reddit/oauth2_handler.py +151 -0
  17. hyperpocket/auth/reddit/oauth2_schema.py +18 -0
  18. hyperpocket/auth/slack/token_context.py +1 -1
  19. hyperpocket/builtin.py +63 -0
  20. hyperpocket/cli/__main__.py +12 -0
  21. hyperpocket/cli/auth.py +83 -0
  22. hyperpocket/cli/codegen/auth/__init__.py +13 -0
  23. hyperpocket/cli/codegen/auth/auth_context_template.py +16 -0
  24. hyperpocket/cli/codegen/auth/auth_token_context_template.py +16 -0
  25. hyperpocket/cli/codegen/auth/auth_token_handler_template.py +69 -0
  26. hyperpocket/cli/codegen/auth/auth_token_schema_template.py +12 -0
  27. hyperpocket/cli/codegen/auth/server_auth_template.py +18 -0
  28. hyperpocket/cli/eject.py +19 -0
  29. hyperpocket/cli/sync.py +5 -5
  30. hyperpocket/config/settings.py +2 -4
  31. hyperpocket/futures/futurestore.py +0 -1
  32. hyperpocket/pocket_auth.py +25 -5
  33. hyperpocket/pocket_core.py +262 -0
  34. hyperpocket/pocket_main.py +125 -171
  35. hyperpocket/prompts.py +6 -8
  36. hyperpocket/repository/__init__.py +2 -2
  37. hyperpocket/repository/lock.py +19 -0
  38. hyperpocket/repository/lockfile.py +19 -13
  39. hyperpocket/repository/repository.py +26 -1
  40. hyperpocket/server/auth/__init__.py +0 -6
  41. hyperpocket/server/auth/gumloop.py +16 -0
  42. hyperpocket/server/auth/notion.py +19 -0
  43. hyperpocket/server/auth/reddit.py +16 -0
  44. hyperpocket/server/server.py +52 -16
  45. hyperpocket/server/tool/dto/script.py +15 -2
  46. hyperpocket/server/tool/wasm.py +20 -8
  47. hyperpocket/session/README.md +2 -2
  48. hyperpocket/session/in_memory.py +18 -5
  49. hyperpocket/session/interface.py +14 -0
  50. hyperpocket/session/redis.py +29 -5
  51. hyperpocket/tool/README.md +16 -12
  52. hyperpocket/tool/__init__.py +4 -3
  53. hyperpocket/tool/function/README.md +39 -10
  54. hyperpocket/tool/function/__init__.py +2 -0
  55. hyperpocket/tool/function/annotation.py +2 -1
  56. hyperpocket/tool/function/tool.py +98 -13
  57. hyperpocket/tool/tests/test_function_tool.py +55 -0
  58. hyperpocket/tool/tests/test_wasm_tool.py +73 -0
  59. hyperpocket/tool/tool.py +65 -2
  60. hyperpocket/tool/wasm/README.md +27 -5
  61. hyperpocket/tool/wasm/script.py +40 -1
  62. hyperpocket/tool/wasm/templates/python.py +32 -14
  63. hyperpocket/tool/wasm/tool.py +21 -18
  64. hyperpocket/tool_like.py +5 -0
  65. hyperpocket/util/__init__.py +1 -1
  66. hyperpocket/util/extract_func_param_desc_from_docstring.py +4 -4
  67. hyperpocket/util/function_to_model.py +5 -2
  68. hyperpocket/util/json_schema_to_model.py +45 -26
  69. {hyperpocket-0.0.2.dist-info → hyperpocket-0.1.8.dist-info}/METADATA +101 -72
  70. hyperpocket-0.1.8.dist-info/RECORD +139 -0
  71. {hyperpocket-0.0.2.dist-info → hyperpocket-0.1.8.dist-info}/WHEEL +1 -1
  72. hyperpocket-0.1.8.dist-info/entry_points.txt +2 -0
  73. hyperpocket/auth/README.KR.md +0 -309
  74. hyperpocket/auth/slack/tests/test_oauth2_handler.py +0 -32
  75. hyperpocket/auth/slack/tests/test_token_handler.py +0 -23
  76. hyperpocket/auth/tests/test_google_oauth2_handler.py +0 -147
  77. hyperpocket/auth/tests/test_slack_oauth2_handler.py +0 -147
  78. hyperpocket/auth/tests/test_slack_token_handler.py +0 -66
  79. hyperpocket/external/__init__.py +0 -7
  80. hyperpocket/external/github_client.py +0 -19
  81. hyperpocket/session/README.KR.md +0 -62
  82. hyperpocket/session/tests/test_in_memory.py +0 -145
  83. hyperpocket/session/tests/test_redis.py +0 -151
  84. hyperpocket/tests/test_pocket.py +0 -116
  85. hyperpocket/tests/test_pocket_auth.py +0 -982
  86. hyperpocket/tool/README.KR.md +0 -68
  87. hyperpocket/tool/builtins/__init__.py +0 -0
  88. hyperpocket/tool/builtins/example/__init__.py +0 -0
  89. hyperpocket/tool/builtins/example/add_tool.py +0 -18
  90. hyperpocket/tool/function/README.KR.md +0 -159
  91. hyperpocket/tool/wasm/README.KR.md +0 -144
  92. hyperpocket-0.0.2.dist-info/RECORD +0 -130
  93. hyperpocket-0.0.2.dist-info/entry_points.txt +0 -3
  94. /hyperpocket/auth/{slack/tests → gumloop}/__init__.py +0 -0
  95. /hyperpocket/auth/{tests → notion}/__init__.py +0 -0
  96. /hyperpocket/{session/tests → auth/reddit}/__init__.py +0 -0
  97. /hyperpocket/{tests → cli/codegen}/__init__.py +0 -0
@@ -1,982 +0,0 @@
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)