matrixone-python-sdk 0.1.0__py3-none-any.whl

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 (122) hide show
  1. matrixone/__init__.py +155 -0
  2. matrixone/account.py +723 -0
  3. matrixone/async_client.py +3913 -0
  4. matrixone/async_metadata_manager.py +311 -0
  5. matrixone/async_orm.py +123 -0
  6. matrixone/async_vector_index_manager.py +633 -0
  7. matrixone/base_client.py +208 -0
  8. matrixone/client.py +4672 -0
  9. matrixone/config.py +452 -0
  10. matrixone/connection_hooks.py +286 -0
  11. matrixone/exceptions.py +89 -0
  12. matrixone/logger.py +782 -0
  13. matrixone/metadata.py +820 -0
  14. matrixone/moctl.py +219 -0
  15. matrixone/orm.py +2277 -0
  16. matrixone/pitr.py +646 -0
  17. matrixone/pubsub.py +771 -0
  18. matrixone/restore.py +411 -0
  19. matrixone/search_vector_index.py +1176 -0
  20. matrixone/snapshot.py +550 -0
  21. matrixone/sql_builder.py +844 -0
  22. matrixone/sqlalchemy_ext/__init__.py +161 -0
  23. matrixone/sqlalchemy_ext/adapters.py +163 -0
  24. matrixone/sqlalchemy_ext/dialect.py +534 -0
  25. matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
  26. matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
  27. matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
  28. matrixone/sqlalchemy_ext/ivf_config.py +252 -0
  29. matrixone/sqlalchemy_ext/table_builder.py +351 -0
  30. matrixone/sqlalchemy_ext/vector_index.py +1721 -0
  31. matrixone/sqlalchemy_ext/vector_type.py +948 -0
  32. matrixone/version.py +580 -0
  33. matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
  34. matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
  35. matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
  36. matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
  37. matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
  38. matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
  39. tests/__init__.py +19 -0
  40. tests/offline/__init__.py +20 -0
  41. tests/offline/conftest.py +77 -0
  42. tests/offline/test_account.py +703 -0
  43. tests/offline/test_async_client_query_comprehensive.py +1218 -0
  44. tests/offline/test_basic.py +54 -0
  45. tests/offline/test_case_sensitivity.py +227 -0
  46. tests/offline/test_connection_hooks_offline.py +287 -0
  47. tests/offline/test_dialect_schema_handling.py +609 -0
  48. tests/offline/test_explain_methods.py +346 -0
  49. tests/offline/test_filter_logical_in.py +237 -0
  50. tests/offline/test_fulltext_search_comprehensive.py +795 -0
  51. tests/offline/test_ivf_config.py +249 -0
  52. tests/offline/test_join_methods.py +281 -0
  53. tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
  54. tests/offline/test_logical_in_method.py +237 -0
  55. tests/offline/test_matrixone_version_parsing.py +264 -0
  56. tests/offline/test_metadata_offline.py +557 -0
  57. tests/offline/test_moctl.py +300 -0
  58. tests/offline/test_moctl_simple.py +251 -0
  59. tests/offline/test_model_support_offline.py +359 -0
  60. tests/offline/test_model_support_simple.py +225 -0
  61. tests/offline/test_pinecone_filter_offline.py +377 -0
  62. tests/offline/test_pitr.py +585 -0
  63. tests/offline/test_pubsub.py +712 -0
  64. tests/offline/test_query_update.py +283 -0
  65. tests/offline/test_restore.py +445 -0
  66. tests/offline/test_snapshot_comprehensive.py +384 -0
  67. tests/offline/test_sql_escaping_edge_cases.py +551 -0
  68. tests/offline/test_sqlalchemy_integration.py +382 -0
  69. tests/offline/test_sqlalchemy_vector_integration.py +434 -0
  70. tests/offline/test_table_builder.py +198 -0
  71. tests/offline/test_unified_filter.py +398 -0
  72. tests/offline/test_unified_transaction.py +495 -0
  73. tests/offline/test_vector_index.py +238 -0
  74. tests/offline/test_vector_operations.py +688 -0
  75. tests/offline/test_vector_type.py +174 -0
  76. tests/offline/test_version_core.py +328 -0
  77. tests/offline/test_version_management.py +372 -0
  78. tests/offline/test_version_standalone.py +652 -0
  79. tests/online/__init__.py +20 -0
  80. tests/online/conftest.py +216 -0
  81. tests/online/test_account_management.py +194 -0
  82. tests/online/test_advanced_features.py +344 -0
  83. tests/online/test_async_client_interfaces.py +330 -0
  84. tests/online/test_async_client_online.py +285 -0
  85. tests/online/test_async_model_insert_online.py +293 -0
  86. tests/online/test_async_orm_online.py +300 -0
  87. tests/online/test_async_simple_query_online.py +802 -0
  88. tests/online/test_async_transaction_simple_query.py +300 -0
  89. tests/online/test_basic_connection.py +130 -0
  90. tests/online/test_client_online.py +238 -0
  91. tests/online/test_config.py +90 -0
  92. tests/online/test_config_validation.py +123 -0
  93. tests/online/test_connection_hooks_new_online.py +217 -0
  94. tests/online/test_dialect_schema_handling_online.py +331 -0
  95. tests/online/test_filter_logical_in_online.py +374 -0
  96. tests/online/test_fulltext_comprehensive.py +1773 -0
  97. tests/online/test_fulltext_label_online.py +433 -0
  98. tests/online/test_fulltext_search_online.py +842 -0
  99. tests/online/test_ivf_stats_online.py +506 -0
  100. tests/online/test_logger_integration.py +311 -0
  101. tests/online/test_matrixone_query_orm.py +540 -0
  102. tests/online/test_metadata_online.py +579 -0
  103. tests/online/test_model_insert_online.py +255 -0
  104. tests/online/test_mysql_driver_validation.py +213 -0
  105. tests/online/test_orm_advanced_features.py +2022 -0
  106. tests/online/test_orm_cte_integration.py +269 -0
  107. tests/online/test_orm_online.py +270 -0
  108. tests/online/test_pinecone_filter.py +708 -0
  109. tests/online/test_pubsub_operations.py +352 -0
  110. tests/online/test_query_methods.py +225 -0
  111. tests/online/test_query_update_online.py +433 -0
  112. tests/online/test_search_vector_index.py +557 -0
  113. tests/online/test_simple_fulltext_online.py +915 -0
  114. tests/online/test_snapshot_comprehensive.py +998 -0
  115. tests/online/test_sqlalchemy_engine_integration.py +336 -0
  116. tests/online/test_sqlalchemy_integration.py +425 -0
  117. tests/online/test_transaction_contexts.py +1219 -0
  118. tests/online/test_transaction_insert_methods.py +356 -0
  119. tests/online/test_transaction_query_methods.py +288 -0
  120. tests/online/test_unified_filter_online.py +529 -0
  121. tests/online/test_vector_comprehensive.py +706 -0
  122. tests/online/test_version_management.py +291 -0
@@ -0,0 +1,703 @@
1
+ # Copyright 2021 - 2022 Matrix Origin
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ Unit tests for MatrixOne Account Management functionality
17
+ """
18
+
19
+ import unittest
20
+ import pytest
21
+ from unittest.mock import Mock, patch, AsyncMock
22
+ from datetime import datetime
23
+ from matrixone.account import AccountManager, Account, User, TransactionAccountManager
24
+ from matrixone.async_client import AsyncAccountManager, AsyncTransactionAccountManager
25
+ from matrixone.exceptions import AccountError
26
+
27
+
28
+ class TestAccount(unittest.TestCase):
29
+ """Test Account data class"""
30
+
31
+ def test_account_creation(self):
32
+ """Test Account object creation"""
33
+ account = Account(
34
+ name="test_account",
35
+ admin_name="admin_user",
36
+ created_time=datetime.now(),
37
+ status="OPEN",
38
+ comment="Test account",
39
+ )
40
+
41
+ self.assertEqual(account.name, "test_account")
42
+ self.assertEqual(account.admin_name, "admin_user")
43
+ self.assertEqual(account.status, "OPEN")
44
+ self.assertEqual(account.comment, "Test account")
45
+ self.assertIsNotNone(account.created_time)
46
+
47
+ def test_account_str_representation(self):
48
+ """Test Account string representation"""
49
+ account = Account("test_account", "admin_user", status="OPEN")
50
+ str_repr = str(account)
51
+ self.assertIn("test_account", str_repr)
52
+ self.assertIn("admin_user", str_repr)
53
+ self.assertIn("OPEN", str_repr)
54
+
55
+
56
+ class TestUser(unittest.TestCase):
57
+ """Test User data class"""
58
+
59
+ def test_user_creation(self):
60
+ """Test User object creation"""
61
+ user = User(
62
+ name="test_user",
63
+ host="%",
64
+ account="sys",
65
+ created_time=datetime.now(),
66
+ status="OPEN",
67
+ comment="Test user",
68
+ )
69
+
70
+ self.assertEqual(user.name, "test_user")
71
+ self.assertEqual(user.host, "%")
72
+ self.assertEqual(user.account, "sys")
73
+ self.assertEqual(user.status, "OPEN")
74
+ self.assertEqual(user.comment, "Test user")
75
+ self.assertIsNotNone(user.created_time)
76
+
77
+ def test_user_str_representation(self):
78
+ """Test User string representation"""
79
+ user = User("test_user", "localhost", "test_account", status="OPEN")
80
+ str_repr = str(user)
81
+ self.assertIn("test_user", str_repr)
82
+ self.assertIn("localhost", str_repr)
83
+ self.assertIn("test_account", str_repr)
84
+ self.assertIn("OPEN", str_repr)
85
+
86
+
87
+ class TestAccountManager(unittest.TestCase):
88
+ """Test AccountManager functionality"""
89
+
90
+ def setUp(self):
91
+ """Set up test fixtures"""
92
+ self.client = Mock()
93
+ self.client._escape_identifier = lambda x: f"`{x}`"
94
+ self.client._escape_string = lambda x: f"'{x}'"
95
+ self.account_manager = AccountManager(self.client)
96
+
97
+ def test_create_account_success(self):
98
+ """Test successful account creation"""
99
+ # Mock successful execution - first call for CREATE, second call for get_account
100
+ mock_result = Mock()
101
+ mock_result.rows = [('test_account', 'admin_user', datetime.now(), 'OPEN', 'Test account', None, None)]
102
+ self.client.execute = Mock(return_value=mock_result)
103
+
104
+ # Test account creation
105
+ account = self.account_manager.create_account(
106
+ account_name="test_account",
107
+ admin_name="admin_user",
108
+ password="password123",
109
+ comment="Test account",
110
+ )
111
+
112
+ # Verify
113
+ self.assertIsInstance(account, Account)
114
+ self.assertEqual(account.name, "test_account")
115
+ self.assertEqual(account.admin_name, "admin_user")
116
+ self.assertEqual(account.status, "OPEN")
117
+ self.assertEqual(account.comment, "Test account")
118
+
119
+ # Verify SQL was called correctly - check the first call (CREATE ACCOUNT)
120
+ self.assertEqual(self.client.execute.call_count, 2) # CREATE + get_account
121
+ first_call_args = self.client.execute.call_args_list[0][0][0]
122
+ self.assertIn("CREATE ACCOUNT", first_call_args)
123
+ self.assertIn("admin_user", first_call_args)
124
+ self.assertIn("password123", first_call_args)
125
+ self.assertIn("Test account", first_call_args)
126
+
127
+ def test_create_account_failure(self):
128
+ """Test account creation failure"""
129
+ # Mock execution failure
130
+ self.client.execute = Mock(side_effect=Exception("Database error"))
131
+
132
+ # Test account creation failure
133
+ with self.assertRaises(AccountError) as context:
134
+ self.account_manager.create_account(account_name="test_account", admin_name="admin_user", password="password123")
135
+
136
+ self.assertIn("Failed to create account 'test_account'", str(context.exception))
137
+
138
+ def test_drop_account_success(self):
139
+ """Test successful account deletion"""
140
+ # Mock successful execution
141
+ self.client.execute = Mock(return_value=Mock())
142
+
143
+ # Test account deletion
144
+ self.account_manager.drop_account("test_account")
145
+
146
+ # Verify SQL was called correctly
147
+ self.client.execute.assert_called_with("DROP ACCOUNT `test_account`")
148
+
149
+ def test_drop_account_failure(self):
150
+ """Test account deletion failure"""
151
+ # Mock execution failure
152
+ self.client.execute = Mock(side_effect=Exception("Database error"))
153
+
154
+ # Test account deletion failure
155
+ with self.assertRaises(AccountError) as context:
156
+ self.account_manager.drop_account("test_account")
157
+
158
+ self.assertIn("Failed to drop account 'test_account'", str(context.exception))
159
+
160
+ def test_alter_account_success(self):
161
+ """Test successful account alteration"""
162
+ # Mock successful execution and get_account
163
+ mock_result = Mock()
164
+ mock_result.rows = [('test_account', 'admin_user', datetime.now(), 'OPEN', 'Updated comment', None, None)]
165
+ self.client.execute = Mock(return_value=mock_result)
166
+
167
+ # Test account alteration
168
+ account = self.account_manager.alter_account(account_name="test_account", comment="Updated comment")
169
+
170
+ # Verify
171
+ self.assertIsInstance(account, Account)
172
+ self.assertEqual(account.comment, "Updated comment")
173
+
174
+ # Verify SQL was called correctly - check the first call (ALTER ACCOUNT)
175
+ self.assertEqual(self.client.execute.call_count, 2) # ALTER + get_account
176
+ first_call_args = self.client.execute.call_args_list[0][0][0]
177
+ self.assertIn("ALTER ACCOUNT", first_call_args)
178
+ self.assertIn("Updated comment", first_call_args)
179
+
180
+ def test_alter_account_suspend(self):
181
+ """Test account suspension"""
182
+ # Mock successful execution and get_account
183
+ mock_result = Mock()
184
+ mock_result.rows = [
185
+ (
186
+ 'test_account',
187
+ 'admin_user',
188
+ datetime.now(),
189
+ 'SUSPENDED',
190
+ 'Test account',
191
+ datetime.now(),
192
+ 'Security violation',
193
+ )
194
+ ]
195
+ self.client.execute = Mock(return_value=mock_result)
196
+
197
+ # Test account suspension
198
+ account = self.account_manager.alter_account(
199
+ account_name="test_account", suspend=True, suspend_reason="Security violation"
200
+ )
201
+
202
+ # Verify
203
+ self.assertEqual(account.status, "SUSPENDED")
204
+ self.assertEqual(account.suspended_reason, "Security violation")
205
+
206
+ # Verify SQL was called correctly - check the first call (ALTER ACCOUNT)
207
+ self.assertEqual(self.client.execute.call_count, 2) # ALTER + get_account
208
+ first_call_args = self.client.execute.call_args_list[0][0][0]
209
+ self.assertIn("SUSPEND COMMENT", first_call_args)
210
+ self.assertIn("Security violation", first_call_args)
211
+
212
+ def test_get_account_success(self):
213
+ """Test successful account retrieval"""
214
+ # Mock successful execution
215
+ mock_result = Mock()
216
+ mock_result.rows = [('test_account', 'admin_user', datetime.now(), 'OPEN', 'Test account', None, None)]
217
+ self.client.execute = Mock(return_value=mock_result)
218
+
219
+ # Test account retrieval
220
+ account = self.account_manager.get_account("test_account")
221
+
222
+ # Verify
223
+ self.assertIsInstance(account, Account)
224
+ self.assertEqual(account.name, "test_account")
225
+ self.assertEqual(account.admin_name, "admin_user")
226
+
227
+ # Verify SQL was called correctly
228
+ self.client.execute.assert_called_with("SHOW ACCOUNTS")
229
+
230
+ def test_get_account_not_found(self):
231
+ """Test account not found"""
232
+ # Mock empty result
233
+ mock_result = Mock()
234
+ mock_result.rows = []
235
+ self.client.execute = Mock(return_value=mock_result)
236
+
237
+ # Test account not found
238
+ with self.assertRaises(AccountError) as context:
239
+ self.account_manager.get_account("nonexistent_account")
240
+
241
+ self.assertIn("Account 'nonexistent_account' not found", str(context.exception))
242
+
243
+ def test_list_accounts_success(self):
244
+ """Test successful account listing"""
245
+ # Mock successful execution
246
+ mock_result = Mock()
247
+ mock_result.rows = [
248
+ ('account1', 'admin1', datetime.now(), 'OPEN', 'Account 1', None, None),
249
+ (
250
+ 'account2',
251
+ 'admin2',
252
+ datetime.now(),
253
+ 'SUSPENDED',
254
+ 'Account 2',
255
+ datetime.now(),
256
+ 'Security issue',
257
+ ),
258
+ ]
259
+ self.client.execute = Mock(return_value=mock_result)
260
+
261
+ # Test account listing
262
+ accounts = self.account_manager.list_accounts()
263
+
264
+ # Verify
265
+ self.assertEqual(len(accounts), 2)
266
+ self.assertIsInstance(accounts[0], Account)
267
+ self.assertIsInstance(accounts[1], Account)
268
+ self.assertEqual(accounts[0].name, "account1")
269
+ self.assertEqual(accounts[1].name, "account2")
270
+ self.assertEqual(accounts[1].status, "SUSPENDED")
271
+
272
+ def test_create_user_success(self):
273
+ """Test successful user creation"""
274
+ # Mock successful execution - first call for CREATE, second call for get_user
275
+ mock_result = Mock()
276
+ mock_result.rows = [
277
+ (
278
+ 'test_user',
279
+ 'localhost',
280
+ 'test_account',
281
+ datetime.now(),
282
+ 'OPEN',
283
+ 'Test user',
284
+ None,
285
+ None,
286
+ )
287
+ ]
288
+ self.client.execute = Mock(return_value=mock_result)
289
+
290
+ # Test user creation
291
+ user = self.account_manager.create_user(user_name="test_user", password="password123", comment="Test user")
292
+
293
+ # Verify
294
+ self.assertIsInstance(user, User)
295
+ self.assertEqual(user.name, "test_user")
296
+ self.assertEqual(user.host, "%")
297
+ self.assertEqual(user.account, "sys")
298
+ self.assertEqual(user.comment, "Test user")
299
+
300
+ # Verify SQL was called correctly - check the first call (CREATE USER)
301
+ self.assertEqual(self.client.execute.call_count, 1) # CREATE only
302
+ first_call_args = self.client.execute.call_args_list[0][0][0]
303
+ self.assertIn("CREATE USER", first_call_args)
304
+ self.assertIn("test_user", first_call_args)
305
+ self.assertIn("password123", first_call_args)
306
+
307
+ def test_drop_user_success(self):
308
+ """Test successful user deletion"""
309
+ # Mock successful execution
310
+ self.client.execute = Mock(return_value=Mock())
311
+
312
+ # Test user deletion
313
+ self.account_manager.drop_user("test_user")
314
+
315
+ # Verify SQL was called correctly
316
+ call_args = self.client.execute.call_args[0][0]
317
+ self.assertIn("DROP USER", call_args)
318
+ self.assertIn("test_user", call_args)
319
+
320
+ def test_alter_user_success(self):
321
+ """Test successful user alteration"""
322
+ # Mock successful execution - first call for ALTER, second call for get_user
323
+ mock_result = Mock()
324
+ mock_result.rows = [
325
+ (
326
+ 'test_user',
327
+ 'localhost',
328
+ 'test_account',
329
+ datetime.now(),
330
+ 'OPEN',
331
+ 'Updated comment',
332
+ None,
333
+ None,
334
+ )
335
+ ]
336
+ self.client.execute = Mock(return_value=mock_result)
337
+
338
+ # Test user alteration (password change instead of comment)
339
+ user = self.account_manager.alter_user(user_name="test_user", password="new_password")
340
+
341
+ # Verify
342
+ self.assertIsInstance(user, User)
343
+ self.assertEqual(user.name, "test_user")
344
+
345
+ # Verify SQL was called correctly - check the first call (ALTER USER)
346
+ self.assertEqual(self.client.execute.call_count, 1) # ALTER only
347
+ first_call_args = self.client.execute.call_args_list[0][0][0]
348
+ self.assertIn("ALTER USER", first_call_args)
349
+ self.assertIn("test_user", first_call_args)
350
+ self.assertIn("new_password", first_call_args)
351
+
352
+ def test_alter_user_lock(self):
353
+ """Test user locking"""
354
+ # Mock successful execution - first call for ALTER, second call for get_user
355
+ mock_result = Mock()
356
+ mock_result.rows = [
357
+ (
358
+ 'test_user',
359
+ 'localhost',
360
+ 'test_account',
361
+ datetime.now(),
362
+ 'LOCKED',
363
+ 'Test user',
364
+ datetime.now(),
365
+ 'Security violation',
366
+ )
367
+ ]
368
+ self.client.execute = Mock(return_value=mock_result)
369
+
370
+ # Test user locking
371
+ user = self.account_manager.alter_user(user_name="test_user", lock=True, lock_reason="Security violation")
372
+
373
+ # Verify
374
+ self.assertEqual(user.status, "LOCKED")
375
+ self.assertEqual(user.locked_reason, "Security violation")
376
+
377
+ # Verify SQL was called correctly - check the first call (ALTER USER)
378
+ self.assertEqual(self.client.execute.call_count, 1) # ALTER only
379
+ first_call_args = self.client.execute.call_args_list[0][0][0]
380
+ self.assertIn("ALTER USER", first_call_args)
381
+ self.assertIn("LOCK", first_call_args)
382
+
383
+ def test_get_user_success(self):
384
+ """Test successful user retrieval"""
385
+ # Mock successful execution
386
+ mock_result = Mock()
387
+ mock_result.rows = [
388
+ (
389
+ 'test_user',
390
+ 'localhost',
391
+ 'test_account',
392
+ datetime.now(),
393
+ 'OPEN',
394
+ 'Test user',
395
+ None,
396
+ None,
397
+ )
398
+ ]
399
+ self.client.execute = Mock(return_value=mock_result)
400
+
401
+ # Test user retrieval (using list_users instead of get_user)
402
+ users = self.account_manager.list_users()
403
+ user = users[0] if users else None
404
+
405
+ # Verify
406
+ self.assertIsInstance(user, User)
407
+ self.assertEqual(user.name, "test_user")
408
+ self.assertEqual(user.host, "%")
409
+ self.assertEqual(user.account, "sys")
410
+
411
+ def test_list_users_success(self):
412
+ """Test successful user listing"""
413
+ # Mock successful execution (list_users only returns current user)
414
+ mock_result = Mock()
415
+ mock_result.rows = [
416
+ (
417
+ 'current_user',
418
+ '%',
419
+ 'current_account',
420
+ datetime.now(),
421
+ 'OPEN',
422
+ 'Current user',
423
+ None,
424
+ None,
425
+ )
426
+ ]
427
+ self.client.execute = Mock(return_value=mock_result)
428
+
429
+ # Test user listing
430
+ users = self.account_manager.list_users()
431
+
432
+ # Verify
433
+ self.assertEqual(len(users), 1)
434
+ self.assertIsInstance(users[0], User)
435
+ self.assertEqual(users[0].name, "current_user")
436
+
437
+
438
+ class TestTransactionAccountManager(unittest.TestCase):
439
+ """Test TransactionAccountManager functionality"""
440
+
441
+ def setUp(self):
442
+ """Set up test fixtures"""
443
+ self.client = Mock()
444
+ self.client._escape_identifier = lambda x: f"`{x}`"
445
+ self.client._escape_string = lambda x: f"'{x}'"
446
+ self.transaction = Mock()
447
+ self.transaction.client = self.client
448
+ self.transaction.execute = Mock()
449
+ self.transaction_account_manager = TransactionAccountManager(self.transaction)
450
+
451
+ def test_create_account_in_transaction(self):
452
+ """Test account creation within transaction"""
453
+ # Mock successful execution - first call for CREATE, second call for get_account
454
+ mock_result = Mock()
455
+ mock_result.rows = [('test_account', 'admin_user', datetime.now(), 'OPEN', 'Test account', None, None)]
456
+ self.transaction.execute = Mock(return_value=mock_result)
457
+ self.client.execute = Mock(return_value=mock_result)
458
+
459
+ # Test account creation in transaction
460
+ account = self.transaction_account_manager.create_account(
461
+ account_name="test_account",
462
+ admin_name="admin_user",
463
+ password="password123",
464
+ comment="Test account",
465
+ )
466
+
467
+ # Verify
468
+ self.assertIsInstance(account, Account)
469
+ self.assertEqual(account.name, "test_account")
470
+
471
+ # Verify transaction.execute was called - only for CREATE ACCOUNT
472
+ self.assertEqual(self.transaction.execute.call_count, 1) # CREATE only
473
+ self.assertEqual(self.client.execute.call_count, 1) # get_account
474
+ first_call_args = self.transaction.execute.call_args_list[0][0][0]
475
+ self.assertIn("CREATE ACCOUNT", first_call_args)
476
+
477
+
478
+ class TestAsyncAccountManager(unittest.IsolatedAsyncioTestCase):
479
+ """Test AsyncAccountManager functionality"""
480
+
481
+ def setUp(self):
482
+ """Set up test fixtures"""
483
+ self.client = AsyncMock()
484
+ self.client._escape_identifier = lambda x: f"`{x}`"
485
+ self.client._escape_string = lambda x: f"'{x}'"
486
+ self.async_account_manager = AsyncAccountManager(self.client)
487
+
488
+ async def test_async_create_account_success(self):
489
+ """Test successful async account creation"""
490
+ # Mock successful execution
491
+ mock_result = AsyncMock()
492
+ mock_result.rows = [('test_account', 'admin_user', datetime.now(), 'OPEN', 'Test account', None, None)]
493
+ self.client.execute = AsyncMock(return_value=mock_result)
494
+
495
+ # Test async account creation
496
+ account = await self.async_account_manager.create_account(
497
+ account_name="test_account",
498
+ admin_name="admin_user",
499
+ password="password123",
500
+ comment="Test account",
501
+ )
502
+
503
+ # Verify
504
+ self.assertIsInstance(account, Account)
505
+ self.assertEqual(account.name, "test_account")
506
+ self.assertEqual(account.admin_name, "admin_user")
507
+ self.assertEqual(account.status, "OPEN")
508
+ self.assertEqual(account.comment, "Test account")
509
+
510
+ # Verify SQL was called correctly - check the first call (CREATE ACCOUNT)
511
+ self.assertEqual(self.client.execute.call_count, 2) # CREATE + get_account
512
+ first_call_args = self.client.execute.call_args_list[0][0][0]
513
+ self.assertIn("CREATE ACCOUNT", first_call_args)
514
+ self.assertIn("admin_user", first_call_args)
515
+ self.assertIn("password123", first_call_args)
516
+ self.assertIn("Test account", first_call_args)
517
+
518
+ async def test_async_create_account_failure(self):
519
+ """Test async account creation failure"""
520
+ # Mock execution failure
521
+ self.client.execute = AsyncMock(side_effect=Exception("Database error"))
522
+
523
+ # Test async account creation failure
524
+ with self.assertRaises(AccountError) as context:
525
+ await self.async_account_manager.create_account(
526
+ account_name="test_account", admin_name="admin_user", password="password123"
527
+ )
528
+
529
+ self.assertIn("Failed to create account 'test_account'", str(context.exception))
530
+
531
+ async def test_async_drop_account_success(self):
532
+ """Test successful async account deletion"""
533
+ # Mock successful execution
534
+ self.client.execute = AsyncMock(return_value=AsyncMock())
535
+
536
+ # Test async account deletion
537
+ await self.async_account_manager.drop_account("test_account")
538
+
539
+ # Verify SQL was called correctly
540
+ self.client.execute.assert_called_with("DROP ACCOUNT `test_account`")
541
+
542
+ async def test_async_list_accounts_success(self):
543
+ """Test successful async account listing"""
544
+ # Mock successful execution
545
+ mock_result = AsyncMock()
546
+ mock_result.rows = [
547
+ ('account1', 'admin1', datetime.now(), 'OPEN', 'Account 1', None, None),
548
+ (
549
+ 'account2',
550
+ 'admin2',
551
+ datetime.now(),
552
+ 'SUSPENDED',
553
+ 'Account 2',
554
+ datetime.now(),
555
+ 'Security issue',
556
+ ),
557
+ ]
558
+ self.client.execute = AsyncMock(return_value=mock_result)
559
+
560
+ # Test async account listing
561
+ accounts = await self.async_account_manager.list_accounts()
562
+
563
+ # Verify
564
+ self.assertEqual(len(accounts), 2)
565
+ self.assertIsInstance(accounts[0], Account)
566
+ self.assertIsInstance(accounts[1], Account)
567
+ self.assertEqual(accounts[0].name, "account1")
568
+ self.assertEqual(accounts[1].name, "account2")
569
+ self.assertEqual(accounts[1].status, "SUSPENDED")
570
+
571
+ async def test_async_create_user_success(self):
572
+ """Test successful async user creation"""
573
+ # Mock successful execution
574
+ mock_result = AsyncMock()
575
+ mock_result.rows = [
576
+ (
577
+ 'test_user',
578
+ 'localhost',
579
+ 'test_account',
580
+ datetime.now(),
581
+ 'OPEN',
582
+ 'Test user',
583
+ None,
584
+ None,
585
+ )
586
+ ]
587
+ self.client.execute = AsyncMock(return_value=mock_result)
588
+
589
+ # Test async user creation
590
+ user = await self.async_account_manager.create_user(
591
+ user_name="test_user", password="password123", comment="Test user"
592
+ )
593
+
594
+ # Verify
595
+ self.assertIsInstance(user, User)
596
+ self.assertEqual(user.name, "test_user")
597
+ self.assertEqual(user.host, "%")
598
+ self.assertEqual(user.account, "sys")
599
+ self.assertEqual(user.comment, "Test user")
600
+
601
+ # Verify SQL was called correctly - check the first call (CREATE USER)
602
+ self.assertEqual(self.client.execute.call_count, 1) # CREATE only
603
+ first_call_args = self.client.execute.call_args_list[0][0][0]
604
+ self.assertIn("CREATE USER", first_call_args)
605
+ self.assertIn("test_user", first_call_args)
606
+ self.assertIn("password123", first_call_args)
607
+
608
+ async def test_async_list_users_success(self):
609
+ """Test successful async user listing"""
610
+ # Mock successful execution
611
+ mock_result = AsyncMock()
612
+ mock_result.rows = [
613
+ ('user1', 'localhost', 'account1', datetime.now(), 'OPEN', 'User 1', None, None),
614
+ (
615
+ 'user2',
616
+ 'localhost',
617
+ 'account1',
618
+ datetime.now(),
619
+ 'LOCKED',
620
+ 'User 2',
621
+ datetime.now(),
622
+ 'Security issue',
623
+ ),
624
+ ]
625
+ self.client.execute = AsyncMock(return_value=mock_result)
626
+
627
+ # Test async user listing
628
+ users = await self.async_account_manager.list_users()
629
+
630
+ # Verify
631
+ self.assertEqual(len(users), 2)
632
+ self.assertIsInstance(users[0], User)
633
+ self.assertIsInstance(users[1], User)
634
+ self.assertEqual(users[0].name, "user1")
635
+ self.assertEqual(users[1].name, "user2")
636
+ self.assertEqual(users[1].status, "LOCKED")
637
+
638
+
639
+ class TestAsyncTransactionAccountManager(unittest.IsolatedAsyncioTestCase):
640
+ """Test AsyncTransactionAccountManager functionality"""
641
+
642
+ def setUp(self):
643
+ """Set up test fixtures"""
644
+ self.client = AsyncMock()
645
+ self.client._escape_identifier = lambda x: f"`{x}`"
646
+ self.client._escape_string = lambda x: f"'{x}'"
647
+ self.transaction = AsyncMock()
648
+ self.transaction.client = self.client
649
+ self.transaction.execute = AsyncMock()
650
+ self.async_transaction_account_manager = AsyncTransactionAccountManager(self.transaction)
651
+
652
+ async def test_async_transaction_create_account(self):
653
+ """Test account creation within async transaction"""
654
+ # Mock successful execution
655
+ mock_result = AsyncMock()
656
+ mock_result.rows = [('test_account', 'admin_user', datetime.now(), 'OPEN', 'Test account', None, None)]
657
+ self.transaction.execute = AsyncMock(return_value=mock_result)
658
+
659
+ # Test account creation in async transaction
660
+ account = await self.async_transaction_account_manager.create_account(
661
+ account_name="test_account",
662
+ admin_name="admin_user",
663
+ password="password123",
664
+ comment="Test account",
665
+ )
666
+
667
+ # Verify
668
+ self.assertIsInstance(account, Account)
669
+ self.assertEqual(account.name, "test_account")
670
+
671
+ # Verify transaction.execute was called - check the first call (CREATE ACCOUNT)
672
+ self.assertEqual(self.transaction.execute.call_count, 2) # CREATE + get_account
673
+ first_call_args = self.transaction.execute.call_args_list[0][0][0]
674
+ self.assertIn("CREATE ACCOUNT", first_call_args)
675
+
676
+
677
+ if __name__ == '__main__':
678
+ # Run async tests
679
+ import asyncio
680
+
681
+ async def run_async_tests():
682
+ """Run async test methods"""
683
+ test_instance = TestAsyncAccountManager()
684
+ test_instance.setUp()
685
+
686
+ await test_instance.test_async_create_account_success()
687
+ await test_instance.test_async_create_account_failure()
688
+ await test_instance.test_async_drop_account_success()
689
+ await test_instance.test_async_list_accounts_success()
690
+ await test_instance.test_async_create_user_success()
691
+ await test_instance.test_async_list_users_success()
692
+
693
+ test_instance = TestAsyncTransactionAccountManager()
694
+ test_instance.setUp()
695
+ await test_instance.test_async_transaction_create_account()
696
+
697
+ print("✅ All async tests passed!")
698
+
699
+ # Run sync tests
700
+ unittest.main(argv=[''], exit=False, verbosity=2)
701
+
702
+ # Run async tests
703
+ asyncio.run(run_async_tests())