# #region TestSeedSupersetLoadTest [C:2] [TYPE Module] [SEMANTICS test,superset,seed,load-test] # @BRIEF Tests for pure logic functions in seed_superset_load_test.py (no real Superset API). # @RELATION BINDS_TO -> [SeedSupersetLoadTestScript] # @TEST_EDGE: parse_args_defaults -> Returns default values # @TEST_EDGE: parse_args_custom -> Overrides with CLI args # @TEST_EDGE: extract_result_payload_wrapped -> Unwraps 'result' key # @TEST_EDGE: extract_result_payload_plain -> Returns payload as-is # @TEST_EDGE: extract_result_payload_not_dict -> Returns payload # @TEST_EDGE: extract_created_id_direct -> Reads id key # @TEST_EDGE: extract_created_id_nested -> Reads result.id # @TEST_EDGE: extract_created_id_missing -> Returns None # @TEST_EDGE: generate_unique_name_unique -> No collisions # @TEST_EDGE: generate_unique_name_collision -> Skips used names # @TEST_EDGE: resolve_target_envs_ok -> Returns resolved envs # @TEST_EDGE: resolve_target_envs_missing -> Raises ValueError # @TEST_EDGE: seed_superset_load_data_dry_run -> No side effects with --dry-run import sys from pathlib import Path from unittest.mock import MagicMock, patch, PropertyMock import pytest sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src")) # ============================================================================= # _parse_args # ============================================================================= class TestParseArgs: """Tests for seed_superset_load_test._parse_args().""" def test_defaults(self): """Happy: no CLI args returns defaults.""" from src.scripts.seed_superset_load_test import _parse_args with patch("sys.argv", ["prog"]): args = _parse_args() assert args.envs == ["ss1", "ss2"] assert args.charts == 10000 assert args.dashboards == 500 assert args.template_pool_size == 200 assert args.seed is None assert args.max_errors == 100 assert args.dry_run is False def test_custom_values(self): """Edge: all args overridden.""" from src.scripts.seed_superset_load_test import _parse_args argv = [ "prog", "--envs", "prod", "staging", "--charts", "50", "--dashboards", "10", "--template-pool-size", "5", "--seed", "42", "--max-errors", "5", "--dry-run", ] with patch("sys.argv", argv): args = _parse_args() assert args.envs == ["prod", "staging"] assert args.charts == 50 assert args.dashboards == 10 assert args.template_pool_size == 5 assert args.seed == 42 assert args.max_errors == 5 assert args.dry_run is True # ============================================================================= # _extract_result_payload # ============================================================================= class TestExtractResultPayload: """Tests for seed_superset_load_test._extract_result_payload().""" def test_wrapped_in_result(self): """Happy: payload has result dict, returns it.""" from src.scripts.seed_superset_load_test import _extract_result_payload payload = {"result": {"id": 1, "name": "test"}} result = _extract_result_payload(payload) assert result == {"id": 1, "name": "test"} def test_plain_payload(self): """Edge: no result key returns payload as-is.""" from src.scripts.seed_superset_load_test import _extract_result_payload payload = {"id": 1, "name": "test"} result = _extract_result_payload(payload) assert result == payload def test_result_not_dict(self): """Edge: result is a list, returns original payload.""" from src.scripts.seed_superset_load_test import _extract_result_payload payload = {"result": [1, 2, 3]} result = _extract_result_payload(payload) assert result == payload # ============================================================================= # _extract_created_id # ============================================================================= class TestExtractCreatedId: """Tests for seed_superset_load_test._extract_created_id().""" def test_direct_id(self): """Happy: payload has direct id key.""" from src.scripts.seed_superset_load_test import _extract_created_id assert _extract_created_id({"id": 42}) == 42 def test_nested_in_result(self): """Happy: id nested under result key.""" from src.scripts.seed_superset_load_test import _extract_created_id assert _extract_created_id({"result": {"id": 99}}) == 99 def test_missing_id(self): """Edge: no id anywhere returns None.""" from src.scripts.seed_superset_load_test import _extract_created_id assert _extract_created_id({"foo": "bar"}) is None def test_id_not_int(self): """Edge: id is non-int returns None.""" from src.scripts.seed_superset_load_test import _extract_created_id assert _extract_created_id({"id": "abc"}) is None def test_result_without_id(self): """Edge: result exists but has no id.""" from src.scripts.seed_superset_load_test import _extract_created_id assert _extract_created_id({"result": {"foo": "bar"}}) is None # ============================================================================= # _generate_unique_name # ============================================================================= class TestGenerateUniqueName: """Tests for seed_superset_load_test._generate_unique_name().""" def test_generates_unique_names(self): """Happy: generates multiple unique names without collision.""" from src.scripts.seed_superset_load_test import _generate_unique_name import random rng = random.Random(42) used: set[str] = set() names = [_generate_unique_name("lt_chart", used, rng) for _ in range(50)] assert len(names) == 50 assert len(set(names)) == 50 # all unique assert all(n.startswith("lt_chart_") for n in names) def test_collision_avoidance(self): """Edge: generates name, verifies it's added to used set.""" from src.scripts.seed_superset_load_test import _generate_unique_name import random rng = random.Random(42) used: set[str] = set() # Verify the function adds the name to used and returns a valid name name = _generate_unique_name("lt_test", used, rng) assert name.startswith("lt_test_") assert len(name) > len("lt_test_") # The name is added to used by the function assert name in used # Generate another — it should be different name2 = _generate_unique_name("lt_test", used, rng) assert name2 != name assert name2 in used assert len(used) == 2 # ============================================================================= # _resolve_target_envs (mocked config) # ============================================================================= class TestResolveTargetEnvs: """Tests for seed_superset_load_test._resolve_target_envs().""" def _make_env(self, env_id: str, name: str = ""): """Helper to build a minimal Environment with required fields.""" from src.core.config_models import Environment return Environment( id=env_id, name=name or env_id, url=f"http://{env_id}.test", username="test_user", password="test_pass", ) @patch("src.scripts.seed_superset_load_test.ConfigManager") def test_resolves_configured_envs(self, MockConfigManager): """Happy: returns resolved env objects.""" from src.scripts.seed_superset_load_test import _resolve_target_envs env1 = self._make_env("ss1", "Env1") env2 = self._make_env("ss2", "Env2") mock_mgr = MockConfigManager.return_value mock_mgr.get_environments.return_value = [env1, env2] result = _resolve_target_envs(["ss1", "ss2"]) assert "ss1" in result assert "ss2" in result assert result["ss1"].name == "Env1" @patch("src.scripts.seed_superset_load_test.ConfigManager") def test_missing_env_raises(self, MockConfigManager): """Edge: requested env not in config raises ValueError.""" from src.scripts.seed_superset_load_test import _resolve_target_envs env1 = self._make_env("ss1") mock_mgr = MockConfigManager.return_value mock_mgr.get_environments.return_value = [env1] with pytest.raises(ValueError, match="Environment 'ss2' not found"): _resolve_target_envs(["ss1", "ss2"]) @patch("src.scripts.seed_superset_load_test.ConfigManager") def test_no_environments_empty_result(self, MockConfigManager): """Edge: no configured envs returns empty dict, no error for empty request.""" from src.scripts.seed_superset_load_test import _resolve_target_envs mock_mgr = MockConfigManager.return_value mock_mgr.get_environments.return_value = [] # Empty request: no error result = _resolve_target_envs([]) assert result == {} @patch("src.scripts.seed_superset_load_test.ConfigManager") def test_partial_resolution(self, MockConfigManager): """Edge: only some envs resolved, raises on missing.""" from src.scripts.seed_superset_load_test import _resolve_target_envs env1 = self._make_env("ss1") mock_mgr = MockConfigManager.return_value mock_mgr.get_environments.return_value = [env1] with pytest.raises(ValueError, match="Environment 'ss2' not found"): _resolve_target_envs(["ss1", "ss2"]) # ============================================================================= # seed_superset_load_data (dry_run mode only — no real API calls) # ============================================================================= class TestSeedSupersetLoadData: """Tests for seed_superset_load_data — dry run and early returns.""" def _make_env(self, env_id: str, name: str = ""): """Helper to build a minimal Environment with required fields.""" from src.core.config_models import Environment return Environment( id=env_id, name=name or env_id, url=f"http://{env_id}.test", username="test_user", password="test_pass", ) @patch("src.scripts.seed_superset_load_test._resolve_target_envs") @patch("src.scripts.seed_superset_load_test.SupersetClient") def test_dry_run_returns_summary(self, MockClient, mock_resolve): """Happy: dry_run=True returns summary without API writes.""" from src.scripts.seed_superset_load_test import seed_superset_load_data env1 = self._make_env("ss1", "Env1") mock_resolve.return_value = {"ss1": env1} # Mock client mock_client = MagicMock() mock_client.network.fetch_paginated_data.return_value = [ {"id": 1, "slice_name": "chart1", "viz_type": "bar"} ] mock_client.get_chart.return_value = { "result": {"datasource_id": 10, "datasource_type": "table", "params": "{}"} } MockClient.return_value = mock_client args = MagicMock() args.seed = 42 args.envs = ["ss1"] args.charts = 100 args.dashboards = 10 args.template_pool_size = 5 args.max_errors = 100 args.dry_run = True result = seed_superset_load_data(args) assert result["dry_run"] is True assert result["charts_target"] == 100 assert result["dashboards_target"] == 10 assert result["templates_by_env"] == {"ss1": 1} # In dry_run mode, no POST requests are made mock_client.network.request.assert_not_called() # #endregion TestSeedSupersetLoadTest