|
3 | 3 | import logging |
4 | 4 | import os |
5 | 5 | import tempfile |
| 6 | +import time |
6 | 7 | from unittest import TestCase |
7 | 8 |
|
8 | 9 | import boto3 |
9 | 10 | from src.superannotate import AppException |
10 | 11 | from src.superannotate import SAClient |
11 | 12 | from tests import compare_result |
| 13 | +from tests.integration.base import BaseTestCase |
12 | 14 | from tests.integration.export import DATA_SET_PATH |
13 | 15 |
|
14 | 16 | sa = SAClient() |
@@ -122,3 +124,156 @@ def test_export_with_statuses(self): |
122 | 124 | sa.download_export(self.PROJECT_NAME, export, tmpdir_name) |
123 | 125 | assert not filecmp.dircmp(tmpdir_name, self.TEST_FOLDER_PATH).left_only |
124 | 126 | assert not filecmp.dircmp(tmpdir_name, self.TEST_FOLDER_PATH).right_only |
| 127 | + |
| 128 | + |
| 129 | +class TestDeleteExports(BaseTestCase): |
| 130 | + PROJECT_NAME = "TestDeleteExports" |
| 131 | + PROJECT_DESCRIPTION = "Desc" |
| 132 | + PROJECT_TYPE = "Vector" |
| 133 | + |
| 134 | + def _check_all_exports_prepared(self, timeout=60): |
| 135 | + """ |
| 136 | + Wait for all exports to be prepared and return them. |
| 137 | +
|
| 138 | + :param timeout: Maximum time to wait in seconds |
| 139 | + :return: List of all exports when all are prepared |
| 140 | + :raises TimeoutError: If exports are not ready within timeout |
| 141 | + """ |
| 142 | + start_time = time.time() |
| 143 | + while time.time() - start_time < timeout: |
| 144 | + exports = sa.get_exports(self.PROJECT_NAME, return_metadata=True) |
| 145 | + |
| 146 | + # Check if all exports are in a final state (not InProgress) |
| 147 | + all_ready = all(exp.get("status") == 2 for exp in exports) |
| 148 | + |
| 149 | + if all_ready: |
| 150 | + return exports |
| 151 | + |
| 152 | + time.sleep(2) |
| 153 | + |
| 154 | + raise TimeoutError("Exports did not complete within the timeout period") |
| 155 | + |
| 156 | + def test_delete_export_by_name(self): |
| 157 | + """Test deleting a single export by name""" |
| 158 | + export1 = sa.prepare_export(self.PROJECT_NAME) |
| 159 | + exports = self._check_all_exports_prepared() |
| 160 | + assert len(exports) == 1 |
| 161 | + |
| 162 | + with self.assertLogs("sa", level="INFO") as cm: |
| 163 | + sa.delete_exports(self.PROJECT_NAME, exports=[export1["name"]]) |
| 164 | + assert "INFO:sa:Successfully removed 1 export(s)." in cm.output[0] |
| 165 | + exports = sa.get_exports(self.PROJECT_NAME, return_metadata=True) |
| 166 | + export_names = [exp["name"] for exp in exports] |
| 167 | + |
| 168 | + assert export1["name"] not in export_names |
| 169 | + |
| 170 | + def test_delete_export_by_id(self): |
| 171 | + """Test deleting a single export by ID""" |
| 172 | + export1 = sa.prepare_export(self.PROJECT_NAME) |
| 173 | + exports = self._check_all_exports_prepared() |
| 174 | + assert len(exports) == 1 |
| 175 | + |
| 176 | + export2 = sa.prepare_export(self.PROJECT_NAME) |
| 177 | + exports = self._check_all_exports_prepared() |
| 178 | + assert len(exports) == 2 |
| 179 | + |
| 180 | + sa.delete_exports(self.PROJECT_NAME, exports=[export1["id"]]) |
| 181 | + exports = sa.get_exports(self.PROJECT_NAME, return_metadata=True) |
| 182 | + export_ids = [exp["id"] for exp in exports] |
| 183 | + |
| 184 | + assert export1["id"] not in export_ids |
| 185 | + assert export2["id"] in export_ids |
| 186 | + |
| 187 | + def test_delete_multiple_exports(self): |
| 188 | + """Test deleting multiple exports by name and ID""" |
| 189 | + export1 = sa.prepare_export(self.PROJECT_NAME) |
| 190 | + exports = self._check_all_exports_prepared() |
| 191 | + assert len(exports) == 1 |
| 192 | + |
| 193 | + export2 = sa.prepare_export(self.PROJECT_NAME) |
| 194 | + exports = self._check_all_exports_prepared() |
| 195 | + assert len(exports) == 2 |
| 196 | + |
| 197 | + export3 = sa.prepare_export(self.PROJECT_NAME) |
| 198 | + exports = self._check_all_exports_prepared() |
| 199 | + assert len(exports) == 3 |
| 200 | + |
| 201 | + sa.delete_exports(self.PROJECT_NAME, exports=[export1["id"], export2["id"]]) |
| 202 | + |
| 203 | + exports = sa.get_exports(self.PROJECT_NAME, return_metadata=True) |
| 204 | + export_ids = [exp["id"] for exp in exports] |
| 205 | + |
| 206 | + assert export1["id"] not in export_ids |
| 207 | + assert export2["id"] not in export_ids |
| 208 | + assert export3["id"] in export_ids |
| 209 | + |
| 210 | + def test_delete_all_exports(self): |
| 211 | + """Test deleting all exports using '*'""" |
| 212 | + export1 = sa.prepare_export(self.PROJECT_NAME) |
| 213 | + exports = self._check_all_exports_prepared() |
| 214 | + assert len(exports) == 1 |
| 215 | + |
| 216 | + export2 = sa.prepare_export(self.PROJECT_NAME) |
| 217 | + exports = self._check_all_exports_prepared() |
| 218 | + assert len(exports) == 2 |
| 219 | + |
| 220 | + sa.delete_exports(self.PROJECT_NAME, exports="*") |
| 221 | + |
| 222 | + exports = sa.get_exports(self.PROJECT_NAME, return_metadata=True) |
| 223 | + assert len(exports) == 0 |
| 224 | + |
| 225 | + def test_delete_nonexistent_export(self): |
| 226 | + """Test deleting a non-existent export (should not raise error)""" |
| 227 | + export1 = sa.prepare_export(self.PROJECT_NAME) |
| 228 | + exports = self._check_all_exports_prepared() |
| 229 | + assert len(exports) == 1 |
| 230 | + |
| 231 | + # Should not raise error for non-existent export |
| 232 | + with self.assertLogs("sa", level="INFO") as cm: |
| 233 | + sa.delete_exports( |
| 234 | + self.PROJECT_NAME, exports=["nonexistent_export", export1["name"]] |
| 235 | + ) |
| 236 | + assert "Successfully removed 1 export(s)." in cm.output[0] |
| 237 | + |
| 238 | + exports = sa.get_exports(self.PROJECT_NAME, return_metadata=True) |
| 239 | + export_names = [exp["name"] for exp in exports] |
| 240 | + |
| 241 | + assert export1["name"] not in export_names |
| 242 | + |
| 243 | + def test_delete_exports_empty_list(self): |
| 244 | + """Test deleting with empty list""" |
| 245 | + export1 = sa.prepare_export(self.PROJECT_NAME) |
| 246 | + exports = self._check_all_exports_prepared() |
| 247 | + assert len(exports) == 1 |
| 248 | + |
| 249 | + with self.assertLogs("sa", level="INFO") as cm: |
| 250 | + sa.delete_exports(self.PROJECT_NAME, exports=[]) |
| 251 | + assert "Successfully removed 0 export(s)." in cm.output[0] |
| 252 | + |
| 253 | + exports = sa.get_exports(self.PROJECT_NAME, return_metadata=True) |
| 254 | + assert len(exports) == 1 |
| 255 | + |
| 256 | + def test_delete_exports_project_not_found(self): |
| 257 | + """Test deleting exports from non-existent project""" |
| 258 | + with self.assertRaisesRegexp(AppException, "Project not found"): |
| 259 | + sa.delete_exports("NonExistentProject123456", exports=["*"]) |
| 260 | + |
| 261 | + def test_delete_mixed_valid_invalid_exports(self): |
| 262 | + """Test deleting mix of valid and invalid export identifiers""" |
| 263 | + export1 = sa.prepare_export(self.PROJECT_NAME) |
| 264 | + exports = self._check_all_exports_prepared() |
| 265 | + assert len(exports) == 1 |
| 266 | + |
| 267 | + export2 = sa.prepare_export(self.PROJECT_NAME) |
| 268 | + exports = self._check_all_exports_prepared() |
| 269 | + assert len(exports) == 2 |
| 270 | + |
| 271 | + with self.assertLogs("sa", level="INFO") as cm: |
| 272 | + sa.delete_exports( |
| 273 | + self.PROJECT_NAME, |
| 274 | + exports=[export1["name"], "invalid_name", 99999, export2["id"]], |
| 275 | + ) |
| 276 | + assert "Successfully removed 2 export(s)." in cm.output[0] |
| 277 | + |
| 278 | + exports = sa.get_exports(self.PROJECT_NAME, return_metadata=True) |
| 279 | + assert len(exports) == 0 |
0 commit comments