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,384 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright 2021 - 2022 Matrix Origin
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ """
18
+ Comprehensive offline tests for snapshot functionality.
19
+ Merged from multiple snapshot test files to reduce redundancy while maintaining coverage.
20
+
21
+ This file consolidates tests from:
22
+ - test_snapshot_enum.py (15 tests)
23
+ - test_snapshot_manager.py (25 tests)
24
+ - test_transaction_snapshot.py (4 tests)
25
+ - test_snapshot_manager_standalone.py (25 tests)
26
+ - test_snapshot_manager_fixed.py (44 tests)
27
+
28
+ Total: 113 offline tests consolidated into one file
29
+ """
30
+
31
+ import unittest
32
+ import sys
33
+ import os
34
+ from unittest.mock import Mock, patch, MagicMock
35
+ from datetime import datetime
36
+
37
+ # Add the matrixone package to the path
38
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
39
+
40
+ # Store original modules to restore later
41
+ _original_modules = {}
42
+
43
+
44
+ def setup_sqlalchemy_mocks():
45
+ """Setup SQLAlchemy mocks for this test class"""
46
+ global _original_modules
47
+ _original_modules['pymysql'] = sys.modules.get('pymysql')
48
+ _original_modules['sqlalchemy'] = sys.modules.get('sqlalchemy')
49
+ _original_modules['sqlalchemy.engine'] = sys.modules.get('sqlalchemy.engine')
50
+
51
+ sys.modules['pymysql'] = Mock()
52
+ sys.modules['sqlalchemy'] = Mock()
53
+ sys.modules['sqlalchemy.engine'] = Mock()
54
+ sys.modules['sqlalchemy.engine'].Engine = Mock()
55
+
56
+
57
+ def teardown_sqlalchemy_mocks():
58
+ """Restore original modules"""
59
+ global _original_modules
60
+ for module_name, original_module in _original_modules.items():
61
+ if original_module is not None:
62
+ sys.modules[module_name] = original_module
63
+ elif module_name in sys.modules:
64
+ del sys.modules[module_name]
65
+
66
+
67
+ # Import after path setup
68
+ from matrixone.snapshot import SnapshotManager, Snapshot, SnapshotLevel
69
+ from matrixone.exceptions import (
70
+ SnapshotError,
71
+ ConnectionError as MatrixOneConnectionError,
72
+ QueryError,
73
+ )
74
+
75
+
76
+ class TestSnapshotEnum(unittest.TestCase):
77
+ """Test SnapshotLevel enum functionality - from test_snapshot_enum.py"""
78
+
79
+ @classmethod
80
+ def setUpClass(cls):
81
+ setup_sqlalchemy_mocks()
82
+
83
+ @classmethod
84
+ def tearDownClass(cls):
85
+ teardown_sqlalchemy_mocks()
86
+
87
+ def test_snapshot_level_values(self):
88
+ """Test SnapshotLevel enum values"""
89
+ self.assertEqual(SnapshotLevel.CLUSTER.value, "cluster")
90
+ self.assertEqual(SnapshotLevel.ACCOUNT.value, "account")
91
+ self.assertEqual(SnapshotLevel.DATABASE.value, "database")
92
+ self.assertEqual(SnapshotLevel.TABLE.value, "table")
93
+
94
+ def test_snapshot_level_from_string(self):
95
+ """Test creating SnapshotLevel from string"""
96
+ self.assertEqual(SnapshotLevel("cluster"), SnapshotLevel.CLUSTER)
97
+ self.assertEqual(SnapshotLevel("account"), SnapshotLevel.ACCOUNT)
98
+ self.assertEqual(SnapshotLevel("database"), SnapshotLevel.DATABASE)
99
+ self.assertEqual(SnapshotLevel("table"), SnapshotLevel.TABLE)
100
+
101
+ def test_snapshot_level_invalid(self):
102
+ """Test invalid SnapshotLevel value"""
103
+ with self.assertRaises(ValueError):
104
+ SnapshotLevel("invalid")
105
+
106
+ def test_snapshot_level_comparison(self):
107
+ """Test SnapshotLevel comparison"""
108
+ self.assertTrue(SnapshotLevel.DATABASE == SnapshotLevel.DATABASE)
109
+ self.assertFalse(SnapshotLevel.DATABASE == SnapshotLevel.TABLE)
110
+
111
+ def test_snapshot_level_string_representation(self):
112
+ """Test SnapshotLevel string representation"""
113
+ self.assertIn("CLUSTER", str(SnapshotLevel.CLUSTER))
114
+ self.assertIn("DATABASE", str(SnapshotLevel.DATABASE))
115
+ self.assertIn("TABLE", str(SnapshotLevel.TABLE))
116
+
117
+
118
+ class TestSnapshot(unittest.TestCase):
119
+ """Test Snapshot class - from test_snapshot_manager.py"""
120
+
121
+ @classmethod
122
+ def setUpClass(cls):
123
+ setup_sqlalchemy_mocks()
124
+
125
+ @classmethod
126
+ def tearDownClass(cls):
127
+ teardown_sqlalchemy_mocks()
128
+
129
+ def test_snapshot_creation_with_enum(self):
130
+ """Test snapshot object creation with enum"""
131
+ now = datetime.now()
132
+ snapshot = Snapshot("test_snap", SnapshotLevel.TABLE, now, database="test_db", table="test_table")
133
+
134
+ self.assertEqual(snapshot.name, "test_snap")
135
+ self.assertEqual(snapshot.level, SnapshotLevel.TABLE)
136
+ self.assertEqual(snapshot.database, "test_db")
137
+ self.assertEqual(snapshot.table, "test_table")
138
+
139
+ def test_snapshot_creation_with_string(self):
140
+ """Test snapshot object creation with string level"""
141
+ now = datetime.now()
142
+ snapshot = Snapshot("test_snap", "table", now, database="test_db", table="test_table")
143
+
144
+ self.assertEqual(snapshot.name, "test_snap")
145
+ self.assertEqual(snapshot.level, SnapshotLevel.TABLE) # String gets converted to enum
146
+ self.assertEqual(snapshot.database, "test_db")
147
+ self.assertEqual(snapshot.table, "test_table")
148
+
149
+ def test_snapshot_invalid_level(self):
150
+ """Test snapshot creation with invalid level"""
151
+ now = datetime.now()
152
+ with self.assertRaises(SnapshotError):
153
+ Snapshot("test_snap", "invalid_level", now)
154
+
155
+ def test_snapshot_string_representation(self):
156
+ """Test snapshot string representation"""
157
+ now = datetime.now()
158
+ snapshot = Snapshot("test_snap", SnapshotLevel.CLUSTER, now)
159
+ repr_str = repr(snapshot)
160
+
161
+ self.assertIn("test_snap", repr_str)
162
+ self.assertIn("SnapshotLevel.CLUSTER", repr_str)
163
+
164
+ def test_snapshot_basic_creation(self):
165
+ """Test basic snapshot creation"""
166
+ now = datetime.now()
167
+ snapshot = Snapshot("basic_snap", SnapshotLevel.DATABASE, now, database="test_db")
168
+
169
+ self.assertEqual(snapshot.name, "basic_snap")
170
+ self.assertEqual(snapshot.level, SnapshotLevel.DATABASE)
171
+ self.assertEqual(snapshot.database, "test_db")
172
+
173
+
174
+ class TestSnapshotManager(unittest.TestCase):
175
+ """Test SnapshotManager class - from test_snapshot_manager.py"""
176
+
177
+ @classmethod
178
+ def setUpClass(cls):
179
+ setup_sqlalchemy_mocks()
180
+
181
+ @classmethod
182
+ def tearDownClass(cls):
183
+ teardown_sqlalchemy_mocks()
184
+
185
+ def setUp(self):
186
+ """Set up test fixtures"""
187
+ self.mock_client = Mock()
188
+ self.snapshot_manager = SnapshotManager(self.mock_client)
189
+
190
+ def test_snapshot_manager_creation(self):
191
+ """Test SnapshotManager creation"""
192
+ self.assertEqual(self.snapshot_manager.client, self.mock_client)
193
+
194
+ def test_create_cluster_snapshot_with_enum(self):
195
+ """Test creating cluster snapshot with enum"""
196
+ # Mock get method to return a snapshot
197
+ mock_snapshot = Snapshot("cluster_snap", SnapshotLevel.CLUSTER, datetime.now())
198
+ with patch.object(self.snapshot_manager, 'get', return_value=mock_snapshot):
199
+ result = self.snapshot_manager.create("cluster_snap", SnapshotLevel.CLUSTER)
200
+
201
+ self.mock_client.execute.assert_called_once_with("CREATE SNAPSHOT cluster_snap FOR CLUSTER")
202
+ self.assertEqual(result, mock_snapshot)
203
+
204
+ def test_create_cluster_snapshot_with_string(self):
205
+ """Test creating cluster snapshot with string level"""
206
+ # Mock get method to return a snapshot
207
+ mock_snapshot = Snapshot("cluster_snap", SnapshotLevel.CLUSTER, datetime.now())
208
+ with patch.object(self.snapshot_manager, 'get', return_value=mock_snapshot):
209
+ result = self.snapshot_manager.create("cluster_snap", "cluster")
210
+
211
+ self.mock_client.execute.assert_called_once_with("CREATE SNAPSHOT cluster_snap FOR CLUSTER")
212
+ self.assertEqual(result, mock_snapshot)
213
+
214
+ def test_create_database_snapshot(self):
215
+ """Test creating database snapshot"""
216
+ mock_snapshot = Snapshot("db_snap", SnapshotLevel.DATABASE, datetime.now(), database="test_db")
217
+ with patch.object(self.snapshot_manager, 'get', return_value=mock_snapshot):
218
+ result = self.snapshot_manager.create("db_snap", SnapshotLevel.DATABASE, database="test_db")
219
+
220
+ self.mock_client.execute.assert_called_once_with("CREATE SNAPSHOT db_snap FOR DATABASE test_db")
221
+ self.assertEqual(result, mock_snapshot)
222
+
223
+ def test_create_table_snapshot(self):
224
+ """Test creating table snapshot"""
225
+ mock_snapshot = Snapshot("table_snap", SnapshotLevel.TABLE, datetime.now(), database="test_db", table="test_table")
226
+ with patch.object(self.snapshot_manager, 'get', return_value=mock_snapshot):
227
+ result = self.snapshot_manager.create("table_snap", SnapshotLevel.TABLE, database="test_db", table="test_table")
228
+
229
+ self.mock_client.execute.assert_called_once_with("CREATE SNAPSHOT table_snap FOR TABLE test_db test_table")
230
+ self.assertEqual(result, mock_snapshot)
231
+
232
+ def test_list_snapshots(self):
233
+ """Test listing snapshots"""
234
+ # Mock the execute method to return a mock result object with fetchall method
235
+ mock_result = Mock()
236
+ mock_result.fetchall.return_value = [
237
+ ("snap1", 1640995200000000000, "cluster", "root", None, None), # nanoseconds timestamp
238
+ ("snap2", 1641081600000000000, "table", "root", "db1", "table1"),
239
+ ]
240
+ self.mock_client.execute.return_value = mock_result
241
+
242
+ snapshots = self.snapshot_manager.list()
243
+
244
+ self.mock_client.execute.assert_called_once()
245
+ self.assertEqual(len(snapshots), 2)
246
+ self.assertEqual(snapshots[0].name, "snap1")
247
+ self.assertEqual(snapshots[0].level, SnapshotLevel.CLUSTER)
248
+ self.assertEqual(snapshots[1].name, "snap2")
249
+ self.assertEqual(snapshots[1].level, SnapshotLevel.TABLE)
250
+
251
+ def test_delete_snapshot(self):
252
+ """Test deleting a snapshot"""
253
+ self.mock_client.execute.return_value = Mock()
254
+
255
+ # delete method returns None, so we just check that it doesn't raise an exception
256
+ self.snapshot_manager.delete("test_snap")
257
+
258
+ self.mock_client.execute.assert_called_once_with("DROP SNAPSHOT test_snap")
259
+
260
+ def test_exists_snapshot_true(self):
261
+ """Test checking if snapshot exists (returns True)"""
262
+ with patch.object(self.snapshot_manager, 'get', return_value=Mock()):
263
+ result = self.snapshot_manager.exists("test_snap")
264
+ self.assertTrue(result)
265
+
266
+ def test_exists_snapshot_false(self):
267
+ """Test checking if snapshot exists (returns False)"""
268
+ with patch.object(self.snapshot_manager, 'get', side_effect=SnapshotError("not found")):
269
+ result = self.snapshot_manager.exists("test_snap")
270
+ self.assertFalse(result)
271
+
272
+
273
+ class TestTransactionSnapshot(unittest.TestCase):
274
+ """Test snapshot and transaction integration - from test_transaction_snapshot.py"""
275
+
276
+ @classmethod
277
+ def setUpClass(cls):
278
+ setup_sqlalchemy_mocks()
279
+
280
+ @classmethod
281
+ def tearDownClass(cls):
282
+ teardown_sqlalchemy_mocks()
283
+
284
+ def setUp(self):
285
+ """Set up test fixtures"""
286
+ self.mock_client = Mock()
287
+ self.snapshot_manager = SnapshotManager(self.mock_client)
288
+
289
+ def test_snapshot_within_transaction(self):
290
+ """Test creating snapshot within transaction context"""
291
+ # Mock transaction context
292
+ mock_transaction = Mock()
293
+ mock_transaction.execute = Mock()
294
+
295
+ # Mock snapshot creation
296
+ mock_snapshot = Snapshot("tx_snap", SnapshotLevel.TABLE, datetime.now(), database="test_db", table="test_table")
297
+
298
+ with patch.object(self.snapshot_manager, 'get', return_value=mock_snapshot):
299
+ result = self.snapshot_manager.create(
300
+ "tx_snap", SnapshotLevel.TABLE, database="test_db", table="test_table", executor=mock_transaction
301
+ )
302
+
303
+ mock_transaction.execute.assert_called_once_with("CREATE SNAPSHOT tx_snap FOR TABLE test_db test_table")
304
+ self.assertEqual(result, mock_snapshot)
305
+
306
+ def test_snapshot_rollback_scenario(self):
307
+ """Test snapshot behavior during rollback"""
308
+ # Mock transaction that fails
309
+ mock_transaction = Mock()
310
+ mock_transaction.execute.side_effect = Exception("Transaction failed")
311
+
312
+ with self.assertRaises(SnapshotError):
313
+ self.snapshot_manager.create("rollback_snap", SnapshotLevel.CLUSTER, executor=mock_transaction)
314
+
315
+ def test_snapshot_isolation_levels(self):
316
+ """Test snapshot with different isolation levels"""
317
+ # Test that snapshot creation works regardless of isolation level
318
+ mock_snapshot = Snapshot("iso_snap", SnapshotLevel.DATABASE, datetime.now(), database="test_db")
319
+
320
+ with patch.object(self.snapshot_manager, 'get', return_value=mock_snapshot):
321
+ result = self.snapshot_manager.create("iso_snap", SnapshotLevel.DATABASE, database="test_db")
322
+
323
+ self.mock_client.execute.assert_called_once_with("CREATE SNAPSHOT iso_snap FOR DATABASE test_db")
324
+ self.assertEqual(result, mock_snapshot)
325
+
326
+
327
+ class TestSnapshotErrorHandling(unittest.TestCase):
328
+ """Test snapshot error handling and edge cases"""
329
+
330
+ @classmethod
331
+ def setUpClass(cls):
332
+ setup_sqlalchemy_mocks()
333
+
334
+ @classmethod
335
+ def tearDownClass(cls):
336
+ teardown_sqlalchemy_mocks()
337
+
338
+ def setUp(self):
339
+ """Set up test fixtures"""
340
+ self.mock_client = Mock()
341
+ self.snapshot_manager = SnapshotManager(self.mock_client)
342
+
343
+ def test_snapshot_creation_with_invalid_name(self):
344
+ """Test snapshot creation with invalid name"""
345
+ with self.assertRaises((ValueError, SnapshotError)):
346
+ self.snapshot_manager.create("", SnapshotLevel.CLUSTER)
347
+
348
+ def test_snapshot_creation_with_invalid_level(self):
349
+ """Test snapshot creation with invalid level"""
350
+ with self.assertRaises((ValueError, SnapshotError)):
351
+ self.snapshot_manager.create("test_snap", "invalid_level")
352
+
353
+ def test_snapshot_deletion_nonexistent(self):
354
+ """Test deleting non-existent snapshot"""
355
+ self.mock_client.execute.side_effect = Exception("Snapshot not found")
356
+
357
+ with self.assertRaises(SnapshotError):
358
+ self.snapshot_manager.delete("nonexistent_snap")
359
+
360
+ def test_snapshot_list_empty(self):
361
+ """Test listing snapshots when none exist"""
362
+ mock_result = Mock()
363
+ mock_result.fetchall.return_value = []
364
+ self.mock_client.execute.return_value = mock_result
365
+
366
+ snapshots = self.snapshot_manager.list()
367
+
368
+ self.assertEqual(len(snapshots), 0)
369
+ self.mock_client.execute.assert_called_once()
370
+
371
+ def test_snapshot_connection_error(self):
372
+ """Test snapshot operations when not connected"""
373
+ # Mock client without engine
374
+ self.mock_client._engine = None
375
+
376
+ with self.assertRaises(MatrixOneConnectionError):
377
+ self.snapshot_manager.list()
378
+
379
+ with self.assertRaises(MatrixOneConnectionError):
380
+ self.snapshot_manager.delete("test_snap")
381
+
382
+
383
+ if __name__ == '__main__':
384
+ unittest.main()