|
19 | 19 |
|
20 | 20 | import pytest |
21 | 21 | import prison |
| 22 | +import uuid |
| 23 | +import yaml |
22 | 24 | from datetime import datetime |
23 | 25 | from freezegun import freeze_time |
| 26 | +from io import BytesIO |
24 | 27 | from sqlalchemy.sql import func |
| 28 | +from typing import Any |
| 29 | +from zipfile import ZipFile |
25 | 30 |
|
26 | 31 | import tests.integration_tests.test_app # noqa: F401 |
27 | 32 | from superset import db |
@@ -399,3 +404,120 @@ def test_delete_bulk_theme_not_found(self): |
399 | 404 | uri = f"api/v1/theme/?q={prison.dumps(theme_ids)}" |
400 | 405 | rv = self.delete_assert_metric(uri, "bulk_delete") |
401 | 406 | assert rv.status_code == 404 |
| 407 | + |
| 408 | + def create_theme_import_zip(self, theme_config: dict[str, Any]) -> BytesIO: |
| 409 | + """Helper method to create a theme import ZIP file""" |
| 410 | + buf = BytesIO() |
| 411 | + with ZipFile(buf, "w") as bundle: |
| 412 | + # Use a root folder like the export does |
| 413 | + root = "theme_import" |
| 414 | + |
| 415 | + # Add metadata.yaml |
| 416 | + metadata = { |
| 417 | + "version": "1.0.0", |
| 418 | + "type": "Theme", |
| 419 | + "timestamp": datetime.now().isoformat(), |
| 420 | + } |
| 421 | + with bundle.open(f"{root}/metadata.yaml", "w") as fp: |
| 422 | + fp.write(yaml.safe_dump(metadata).encode()) |
| 423 | + |
| 424 | + # Add theme YAML file |
| 425 | + theme_yaml = yaml.safe_dump(theme_config) |
| 426 | + with bundle.open( |
| 427 | + f"{root}/themes/{theme_config['theme_name']}.yaml", "w" |
| 428 | + ) as fp: |
| 429 | + fp.write(theme_yaml.encode()) |
| 430 | + buf.seek(0) |
| 431 | + return buf |
| 432 | + |
| 433 | + def test_import_theme(self): |
| 434 | + """ |
| 435 | + Theme API: Test import theme |
| 436 | + """ |
| 437 | + theme_config = { |
| 438 | + "theme_name": "imported_theme", |
| 439 | + "uuid": str(uuid.uuid4()), |
| 440 | + "version": "1.0.0", |
| 441 | + "json_data": {"colors": {"primary": "#007bff"}}, |
| 442 | + } |
| 443 | + |
| 444 | + self.login(ADMIN_USERNAME) |
| 445 | + uri = "api/v1/theme/import/" |
| 446 | + |
| 447 | + buf = self.create_theme_import_zip(theme_config) |
| 448 | + form_data = { |
| 449 | + "formData": (buf, "theme_export.zip"), |
| 450 | + } |
| 451 | + rv = self.client.post(uri, data=form_data, content_type="multipart/form-data") |
| 452 | + response = json.loads(rv.data.decode("utf-8")) |
| 453 | + |
| 454 | + assert rv.status_code == 200 |
| 455 | + assert response == {"message": "Theme imported successfully"} |
| 456 | + |
| 457 | + theme = db.session.query(Theme).filter_by(uuid=theme_config["uuid"]).one() |
| 458 | + assert theme.theme_name == "imported_theme" |
| 459 | + |
| 460 | + # Cleanup |
| 461 | + db.session.delete(theme) |
| 462 | + db.session.commit() |
| 463 | + |
| 464 | + def test_import_theme_overwrite(self): |
| 465 | + """ |
| 466 | + Theme API: Test import existing theme without and with overwrite |
| 467 | + """ |
| 468 | + theme_config = { |
| 469 | + "theme_name": "overwrite_theme", |
| 470 | + "uuid": str(uuid.uuid4()), |
| 471 | + "version": "1.0.0", |
| 472 | + "json_data": {"colors": {"primary": "#007bff"}}, |
| 473 | + } |
| 474 | + |
| 475 | + self.login(ADMIN_USERNAME) |
| 476 | + uri = "api/v1/theme/import/" |
| 477 | + |
| 478 | + # First import |
| 479 | + buf = self.create_theme_import_zip(theme_config) |
| 480 | + form_data = { |
| 481 | + "formData": (buf, "theme_export.zip"), |
| 482 | + } |
| 483 | + rv = self.client.post(uri, data=form_data, content_type="multipart/form-data") |
| 484 | + response = json.loads(rv.data.decode("utf-8")) |
| 485 | + |
| 486 | + assert rv.status_code == 200 |
| 487 | + assert response == {"message": "Theme imported successfully"} |
| 488 | + |
| 489 | + # Import again without overwrite flag - should fail with structured error |
| 490 | + buf = self.create_theme_import_zip(theme_config) |
| 491 | + form_data = { |
| 492 | + "formData": (buf, "theme_export.zip"), |
| 493 | + } |
| 494 | + rv = self.client.post(uri, data=form_data, content_type="multipart/form-data") |
| 495 | + response = json.loads(rv.data.decode("utf-8")) |
| 496 | + |
| 497 | + assert rv.status_code == 422 |
| 498 | + assert len(response["errors"]) == 1 |
| 499 | + error = response["errors"][0] |
| 500 | + assert error["message"].startswith("Error importing theme") |
| 501 | + assert error["error_type"] == "GENERIC_COMMAND_ERROR" |
| 502 | + assert error["level"] == "warning" |
| 503 | + assert f"themes/{theme_config['theme_name']}.yaml" in str(error["extra"]) |
| 504 | + assert "Theme already exists and `overwrite=true` was not passed" in str( |
| 505 | + error["extra"] |
| 506 | + ) |
| 507 | + |
| 508 | + # Import with overwrite flag - should succeed |
| 509 | + buf = self.create_theme_import_zip(theme_config) |
| 510 | + form_data = { |
| 511 | + "formData": (buf, "theme_export.zip"), |
| 512 | + "overwrite": "true", |
| 513 | + } |
| 514 | + rv = self.client.post(uri, data=form_data, content_type="multipart/form-data") |
| 515 | + response = json.loads(rv.data.decode("utf-8")) |
| 516 | + |
| 517 | + assert rv.status_code == 200 |
| 518 | + assert response == {"message": "Theme imported successfully"} |
| 519 | + |
| 520 | + # Cleanup |
| 521 | + theme = db.session.query(Theme).filter_by(uuid=theme_config["uuid"]).one() |
| 522 | + db.session.delete(theme) |
| 523 | + db.session.commit() |
0 commit comments