codeant-ai-for-open-source[bot] commented on code in PR #38370:
URL: https://github.com/apache/superset/pull/38370#discussion_r2880999878


##########
superset/daos/semantic_layer.py:
##########
@@ -32,6 +32,18 @@ class SemanticLayerDAO(BaseDAO[SemanticLayer]):
     # SemanticLayer uses 'uuid' as the primary key field
     id_column_name = "uuid"
 
+    @staticmethod
+    def find_by_uuid(uuid_str: str) -> SemanticLayer | None:
+        return (
+            db.session.query(SemanticLayer)
+            .filter(SemanticLayer.uuid == uuid_str)
+            .one_or_none()
+        )

Review Comment:
   **Suggestion:** The `find_by_uuid` helper passes the raw `uuid_str` directly 
into a `UUIDType(binary=True)` filter; if a client calls any semantic layer 
endpoint with a malformed UUID (e.g. `/api/v1/semantic_layer/not-a-uuid`), 
SQLAlchemy-Utils will attempt to coerce this string to a UUID and raise 
`ValueError`, causing a 500 instead of a clean 404/400 response. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ GET /api/v1/semantic_layer/<uuid> can 500 on bad UUID.
   - ❌ POST /api/v1/semantic_layer/<uuid>/schema/runtime can 500.
   - ⚠️ Invalid client input can crash semantic layer detail retrieval.
   - ⚠️ Invalid client input can crash semantic runtime schema retrieval.
   - ⚠️ Error handling inconsistent with 404 behavior when layer missing.
   ```
   </details>
   
   ```suggestion
       @staticmethod
       def find_by_uuid(uuid_str: str) -> SemanticLayer | None:
           try:
               return (
                   db.session.query(SemanticLayer)
                   .filter(SemanticLayer.uuid == uuid_str)
                   .one_or_none()
               )
           except ValueError:
               # Malformed UUID strings should be treated as "not found" rather 
than erroring
               return None
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Start Superset with this PR code deployed and ensure the 
`SEMANTIC_LAYERS` feature flag
   is enabled (as used in tests in
   `superset/tests/unit_tests/semantic_layers/api_test.py:37-41`).
   
   2. Issue an HTTP GET request to the semantic layer detail endpoint with an 
invalid UUID
   string, e.g. `GET /api/v1/semantic_layer/not-a-uuid`. This endpoint is 
implemented by
   `SemanticLayerRestApi.get` in `superset/semantic_layers/api.py:457-481`, 
where the
   `<uuid>` path segment is passed as the `uuid: str` argument.
   
   3. Inside `SemanticLayerRestApi.get` 
(`superset/semantic_layers/api.py:479`), the code
   calls `layer = SemanticLayerDAO.find_by_uuid(uuid)`, passing the raw string 
`"not-a-uuid"`
   to the DAO helper.
   
   4. `SemanticLayerDAO.find_by_uuid` in 
`superset/daos/semantic_layer.py:35-41` builds a
   SQLAlchemy query `.filter(SemanticLayer.uuid == uuid_str)`. The 
`SemanticLayer.uuid`
   column is defined as `Column(UUIDType(binary=True), primary_key=True, 
default=uuid.uuid4)`
   in `superset/semantic_layers/models.py:120`, so SQLAlchemy-Utils attempts to 
coerce the
   string `"not-a-uuid"` into a UUID object during parameter binding and raises 
`ValueError`
   for the malformed value.
   
   5. This `ValueError` is not caught in `find_by_uuid` or in 
`SemanticLayerRestApi.get`
   (which only checks `if not layer:` after the call), so the exception bubbles 
out of the
   view function wrapped by Flask-AppBuilder's `@safe` decorator
   (`superset/semantic_layers/api.py:167-171, 457-461`). In practice this 
results in the
   request failing with an unhandled server-side error (500) instead of 
returning a clean
   404/400 response for the invalid UUID path parameter.
   
   6. The same unhandled `ValueError` behavior occurs for `POST
   /api/v1/semantic_layer/<uuid>/schema/runtime`, implemented by
   `SemanticLayerRestApi.runtime_schema` in 
`superset/semantic_layers/api.py:234-282`, which
   also calls `SemanticLayerDAO.find_by_uuid(uuid)` at line 264 without 
guarding against
   malformed UUID strings.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/daos/semantic_layer.py
   **Line:** 35:41
   **Comment:**
        *Logic Error: The `find_by_uuid` helper passes the raw `uuid_str` 
directly into a `UUIDType(binary=True)` filter; if a client calls any semantic 
layer endpoint with a malformed UUID (e.g. 
`/api/v1/semantic_layer/not-a-uuid`), SQLAlchemy-Utils will attempt to coerce 
this string to a UUID and raise `ValueError`, causing a 500 instead of a clean 
404/400 response.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38370&comment_hash=7b391236d4610c78e0cb0d7d26b2b57ca3a34fe75548871058b38f68efeeb0ea&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38370&comment_hash=7b391236d4610c78e0cb0d7d26b2b57ca3a34fe75548871058b38f68efeeb0ea&reaction=dislike'>👎</a>



##########
tests/unit_tests/semantic_layers/api_test.py:
##########
@@ -259,3 +265,624 @@ def test_put_semantic_view_empty_payload(
     )
 
     assert response.status_code == 200
+
+
+# =============================================================================
+# SemanticLayerRestApi tests
+# =============================================================================
+
+
+def test_get_types(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test GET /types returns registered semantic layer types."""
+    mock_cls = MagicMock()
+    mock_cls.name = "Snowflake Semantic Layer"
+    mock_cls.description = "Connect to Snowflake."
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.get("/api/v1/semantic_layer/types")
+
+    assert response.status_code == 200
+    result = response.json["result"]
+    assert len(result) == 1
+    assert result[0] == {
+        "id": "snowflake",
+        "name": "Snowflake Semantic Layer",
+        "description": "Connect to Snowflake.",
+    }
+
+
+def test_get_types_empty(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test GET /types returns empty list when no types registered."""
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {},
+        clear=True,
+    )
+
+    response = client.get("/api/v1/semantic_layer/types")
+
+    assert response.status_code == 200
+    assert response.json["result"] == []
+
+
+def test_configuration_schema(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /schema/configuration returns schema without partial 
config."""
+    mock_cls = MagicMock()
+    mock_cls.get_configuration_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "snowflake"},
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"] == {"type": "object"}
+    mock_cls.get_configuration_schema.assert_called_once_with(None)
+
+
+def test_configuration_schema_with_partial_config(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /schema/configuration enriches schema with partial config."""
+    mock_instance = MagicMock()
+    mock_instance.configuration = {"account": "test"}
+
+    mock_cls = MagicMock()
+    mock_cls.from_configuration.return_value = mock_instance
+    mock_cls.get_configuration_schema.return_value = {
+        "type": "object",
+        "properties": {"database": {"enum": ["db1", "db2"]}},
+    }
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "snowflake", "configuration": {"account": "test"}},
+    )
+
+    assert response.status_code == 200
+    mock_cls.get_configuration_schema.assert_called_once_with({"account": 
"test"})
+
+
+def test_configuration_schema_with_invalid_partial_config(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test /schema/configuration returns schema when partial config fails."""
+    mock_cls = MagicMock()
+    mock_cls.from_configuration.side_effect = ValueError("bad config")
+    mock_cls.get_configuration_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "snowflake", "configuration": {"bad": "data"}},
+    )
+
+    assert response.status_code == 200
+    mock_cls.get_configuration_schema.assert_called_once_with(None)
+
+
+def test_configuration_schema_unknown_type(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /schema/configuration returns 400 for unknown type."""
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "nonexistent"},
+    )
+
+    assert response.status_code == 400
+
+
+def test_runtime_schema(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime returns runtime schema."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "snowflake"
+    mock_layer.implementation.configuration = {"account": "test"}
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mock_cls = MagicMock()
+    mock_cls.get_runtime_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+        json={"runtime_data": {"database": "mydb"}},
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"] == {"type": "object"}
+    mock_cls.get_runtime_schema.assert_called_once_with(
+        {"account": "test"}, {"database": "mydb"}
+    )
+
+
+def test_runtime_schema_no_body(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime works without a request body."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "snowflake"
+    mock_layer.implementation.configuration = {"account": "test"}
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mock_cls = MagicMock()
+    mock_cls.get_runtime_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+    )
+
+    assert response.status_code == 200
+    mock_cls.get_runtime_schema.assert_called_once_with({"account": "test"}, 
None)
+
+
+def test_runtime_schema_not_found(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime returns 404 when layer not found."""
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = None
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{uuid_lib.uuid4()}/schema/runtime",
+    )
+
+    assert response.status_code == 404
+
+
+@SEMANTIC_LAYERS_APP
+def test_runtime_schema_unknown_type(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime returns 400 for unknown type."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "unknown_type"
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+    )
+
+    assert response.status_code == 400
+    assert "Unknown type" in response.json["message"]
+
+
+@SEMANTIC_LAYERS_APP
+def test_runtime_schema_exception(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime returns 400 when schema raises."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "snowflake"
+    mock_layer.implementation.configuration = {"account": "test"}
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mock_cls = MagicMock()
+    mock_cls.get_runtime_schema.side_effect = ValueError("Bad config")
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+    )
+
+    assert response.status_code == 400
+    assert "Bad config" in response.json["message"]
+
+
+def test_post_semantic_layer(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST / creates a semantic layer."""
+    test_uuid = uuid_lib.uuid4()
+    new_model = MagicMock()
+    new_model.uuid = test_uuid
+
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.CreateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.return_value = new_model
+
+    payload = {
+        "name": "My Layer",
+        "type": "snowflake",
+        "configuration": {"account": "test"},
+    }
+    response = client.post("/api/v1/semantic_layer/", json=payload)
+
+    assert response.status_code == 201
+    assert response.json["result"]["uuid"] == str(test_uuid)
+    mock_command.assert_called_once_with(payload)
+
+
+def test_post_semantic_layer_invalid(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST / returns 422 when validation fails."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.CreateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = SemanticLayerInvalidError(
+        "Unknown type: bad"
+    )
+
+    payload = {
+        "name": "My Layer",
+        "type": "bad",
+        "configuration": {},
+    }
+    response = client.post("/api/v1/semantic_layer/", json=payload)
+
+    assert response.status_code == 422
+
+
+def test_post_semantic_layer_create_failed(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST / returns 422 when creation fails."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.CreateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = 
SemanticLayerCreateFailedError()
+
+    payload = {
+        "name": "My Layer",
+        "type": "snowflake",
+        "configuration": {"account": "test"},
+    }
+    response = client.post("/api/v1/semantic_layer/", json=payload)
+
+    assert response.status_code == 422
+
+
+def test_post_semantic_layer_missing_required_fields(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST / returns 400 when required fields are missing."""
+    mocker.patch(
+        "superset.semantic_layers.api.CreateSemanticLayerCommand",
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/",
+        json={"name": "Only name"},
+    )
+
+    assert response.status_code == 400
+
+
+def test_put_semantic_layer(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test PUT /<uuid> updates a semantic layer."""
+    test_uuid = uuid_lib.uuid4()
+    changed_model = MagicMock()
+    changed_model.uuid = test_uuid
+
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.UpdateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.return_value = changed_model
+
+    payload = {"name": "Updated Name"}
+    response = client.put(
+        f"/api/v1/semantic_layer/{test_uuid}",
+        json=payload,
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"]["uuid"] == str(test_uuid)
+    mock_command.assert_called_once_with(str(test_uuid), payload)
+
+
+def test_put_semantic_layer_not_found(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test PUT /<uuid> returns 404 when layer not found."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.UpdateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = SemanticLayerNotFoundError()
+
+    response = client.put(
+        f"/api/v1/semantic_layer/{uuid_lib.uuid4()}",
+        json={"name": "New"},
+    )
+
+    assert response.status_code == 404
+
+
+def test_put_semantic_layer_invalid(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test PUT /<uuid> returns 422 when validation fails."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.UpdateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = SemanticLayerInvalidError(
+        "Name already exists"
+    )
+
+    response = client.put(
+        f"/api/v1/semantic_layer/{uuid_lib.uuid4()}",
+        json={"name": "Duplicate"},
+    )
+
+    assert response.status_code == 422
+
+

Review Comment:
   **Suggestion:** The semantic layer create and update tests call 
`/api/v1/semantic_layer/` endpoints that are only registered when the 
`SEMANTIC_LAYERS` feature flag is enabled, but these tests lack the 
`SEMANTIC_LAYERS_APP` parametrization used elsewhere, so the endpoints may be 
missing and the tests will fail with 404. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Semantic layer create tests can fail with 404 responses.
   - ❌ Update tests may fail when SEMANTIC_LAYERS is disabled.
   - ⚠️ Create/update behavior for semantic layers not reliably tested.
   ```
   </details>
   
   ```suggestion
   @SEMANTIC_LAYERS_APP
   def test_post_semantic_layer(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test POST / creates a semantic layer."""
       test_uuid = uuid_lib.uuid4()
       new_model = MagicMock()
       new_model.uuid = test_uuid
   
       mock_command = mocker.patch(
           "superset.semantic_layers.api.CreateSemanticLayerCommand",
       )
       mock_command.return_value.run.return_value = new_model
   
       payload = {
           "name": "My Layer",
           "type": "snowflake",
           "configuration": {"account": "test"},
       }
       response = client.post("/api/v1/semantic_layer/", json=payload)
   
       assert response.status_code == 201
       assert response.json["result"]["uuid"] == str(test_uuid)
       mock_command.assert_called_once_with(payload)
   
   
   @SEMANTIC_LAYERS_APP
   def test_post_semantic_layer_invalid(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test POST / returns 422 when validation fails."""
       mock_command = mocker.patch(
           "superset.semantic_layers.api.CreateSemanticLayerCommand",
       )
       mock_command.return_value.run.side_effect = SemanticLayerInvalidError(
           "Unknown type: bad"
       )
   
       payload = {
           "name": "My Layer",
           "type": "bad",
           "configuration": {},
       }
       response = client.post("/api/v1/semantic_layer/", json=payload)
   
       assert response.status_code == 422
   
   
   @SEMANTIC_LAYERS_APP
   def test_post_semantic_layer_create_failed(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test POST / returns 422 when creation fails."""
       mock_command = mocker.patch(
           "superset.semantic_layers.api.CreateSemanticLayerCommand",
       )
       mock_command.return_value.run.side_effect = 
SemanticLayerCreateFailedError()
   
       payload = {
           "name": "My Layer",
           "type": "snowflake",
           "configuration": {"account": "test"},
       }
       response = client.post("/api/v1/semantic_layer/", json=payload)
   
       assert response.status_code == 422
   
   
   @SEMANTIC_LAYERS_APP
   def test_post_semantic_layer_missing_required_fields(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test POST / returns 400 when required fields are missing."""
       mocker.patch(
           "superset.semantic_layers.api.CreateSemanticLayerCommand",
       )
   
       response = client.post(
           "/api/v1/semantic_layer/",
           json={"name": "Only name"},
       )
   
       assert response.status_code == 400
   
   
   @SEMANTIC_LAYERS_APP
   def test_put_semantic_layer(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test PUT /<uuid> updates a semantic layer."""
       test_uuid = uuid_lib.uuid4()
       changed_model = MagicMock()
       changed_model.uuid = test_uuid
   
       mock_command = mocker.patch(
           "superset.semantic_layers.api.UpdateSemanticLayerCommand",
       )
       mock_command.return_value.run.return_value = changed_model
   
       payload = {"name": "Updated Name"}
       response = client.put(
           f"/api/v1/semantic_layer/{test_uuid}",
           json=payload,
       )
   
       assert response.status_code == 200
       assert response.json["result"]["uuid"] == str(test_uuid)
       mock_command.assert_called_once_with(str(test_uuid), payload)
   
   
   @SEMANTIC_LAYERS_APP
   def test_put_semantic_layer_not_found(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test PUT /<uuid> returns 404 when layer not found."""
       mock_command = mocker.patch(
           "superset.semantic_layers.api.UpdateSemanticLayerCommand",
       )
       mock_command.return_value.run.side_effect = SemanticLayerNotFoundError()
   
       response = client.put(
           f"/api/v1/semantic_layer/{uuid_lib.uuid4()}",
           json={"name": "New"},
       )
   
       assert response.status_code == 404
   
   
   @SEMANTIC_LAYERS_APP
   def test_put_semantic_layer_invalid(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test PUT /<uuid> returns 422 when validation fails."""
       mock_command = mocker.patch(
           "superset.semantic_layers.api.UpdateSemanticLayerCommand",
       )
       mock_command.return_value.run.side_effect = SemanticLayerInvalidError(
           "Name already exists"
       )
   
       response = client.put(
           f"/api/v1/semantic_layer/{uuid_lib.uuid4()}",
           json={"name": "Duplicate"},
       )
   
       assert response.status_code == 422
   
   
   @SEMANTIC_LAYERS_APP
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. When unit tests run, the `app` fixture in 
`tests/unit_tests/conftest.py:76-125`
   initializes Superset and calls `SupersetAppInitializer.init_views()`
   (`superset/initialization/__init__.py:145-227`), which registers 
`SemanticLayerRestApi`
   only if `feature_flag_manager.is_feature_enabled("SEMANTIC_LAYERS")` is true 
(lines
   269-276).
   
   2. Tests `test_post_semantic_layer*` and `test_put_semantic_layer*` in
   `tests/unit_tests/semantic_layers/api_test.py:564-735` use the plain 
`client` and
   `full_api_access` fixtures but do not apply the `@SEMANTIC_LAYERS_APP` 
parametrization
   declared at lines 37-41, so the app is created with default feature flags 
instead of
   forcing `SEMANTIC_LAYERS` to `True`.
   
   3. If `SEMANTIC_LAYERS` is disabled by default (as implied by it being gated 
in
   `init_views`), then the `/api/v1/semantic_layer/` POST and 
`/api/v1/semantic_layer/<uuid>`
   PUT routes are not added to the app, and calls like
   `client.post("/api/v1/semantic_layer/", json=payload)` (line 584) or
   `client.put(f"/api/v1/semantic_layer/{test_uuid}", json=payload)` (lines 
668-672) return
   404 instead of the expected 201/200/422/404 codes asserted in the tests.
   
   4. As a result, these create/update tests fail (e.g. `assert 
response.status_code == 201`
   at line 586, `assert response.status_code == 200` at line 674, and status 
422/404
   assertions at lines 611, 632, 650, 695, 716, 735) when the semantic layers 
feature flag is
   not explicitly enabled in the test app, even though the API implementation in
   `superset/semantic_layers/api.py:284-399` is correct.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/semantic_layers/api_test.py
   **Line:** 564:718
   **Comment:**
        *Possible Bug: The semantic layer create and update tests call 
`/api/v1/semantic_layer/` endpoints that are only registered when the 
`SEMANTIC_LAYERS` feature flag is enabled, but these tests lack the 
`SEMANTIC_LAYERS_APP` parametrization used elsewhere, so the endpoints may be 
missing and the tests will fail with 404.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38370&comment_hash=42369c0a21403834de22bc157e3243b196bdbae4da236b102152e61232732d84&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38370&comment_hash=42369c0a21403834de22bc157e3243b196bdbae4da236b102152e61232732d84&reaction=dislike'>👎</a>



##########
tests/unit_tests/semantic_layers/api_test.py:
##########
@@ -259,3 +265,624 @@ def test_put_semantic_view_empty_payload(
     )
 
     assert response.status_code == 200
+
+
+# =============================================================================
+# SemanticLayerRestApi tests
+# =============================================================================
+
+
+def test_get_types(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test GET /types returns registered semantic layer types."""
+    mock_cls = MagicMock()
+    mock_cls.name = "Snowflake Semantic Layer"
+    mock_cls.description = "Connect to Snowflake."
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.get("/api/v1/semantic_layer/types")
+
+    assert response.status_code == 200
+    result = response.json["result"]
+    assert len(result) == 1
+    assert result[0] == {
+        "id": "snowflake",
+        "name": "Snowflake Semantic Layer",
+        "description": "Connect to Snowflake.",
+    }
+
+
+def test_get_types_empty(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test GET /types returns empty list when no types registered."""
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {},
+        clear=True,
+    )
+
+    response = client.get("/api/v1/semantic_layer/types")
+
+    assert response.status_code == 200
+    assert response.json["result"] == []
+
+
+def test_configuration_schema(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /schema/configuration returns schema without partial 
config."""
+    mock_cls = MagicMock()
+    mock_cls.get_configuration_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "snowflake"},
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"] == {"type": "object"}
+    mock_cls.get_configuration_schema.assert_called_once_with(None)
+
+
+def test_configuration_schema_with_partial_config(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /schema/configuration enriches schema with partial config."""
+    mock_instance = MagicMock()
+    mock_instance.configuration = {"account": "test"}
+
+    mock_cls = MagicMock()
+    mock_cls.from_configuration.return_value = mock_instance
+    mock_cls.get_configuration_schema.return_value = {
+        "type": "object",
+        "properties": {"database": {"enum": ["db1", "db2"]}},
+    }
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "snowflake", "configuration": {"account": "test"}},
+    )
+
+    assert response.status_code == 200
+    mock_cls.get_configuration_schema.assert_called_once_with({"account": 
"test"})
+
+
+def test_configuration_schema_with_invalid_partial_config(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test /schema/configuration returns schema when partial config fails."""
+    mock_cls = MagicMock()
+    mock_cls.from_configuration.side_effect = ValueError("bad config")
+    mock_cls.get_configuration_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "snowflake", "configuration": {"bad": "data"}},
+    )
+
+    assert response.status_code == 200
+    mock_cls.get_configuration_schema.assert_called_once_with(None)
+
+
+def test_configuration_schema_unknown_type(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /schema/configuration returns 400 for unknown type."""
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "nonexistent"},
+    )
+
+    assert response.status_code == 400
+
+
+def test_runtime_schema(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime returns runtime schema."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "snowflake"
+    mock_layer.implementation.configuration = {"account": "test"}
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mock_cls = MagicMock()
+    mock_cls.get_runtime_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+        json={"runtime_data": {"database": "mydb"}},
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"] == {"type": "object"}
+    mock_cls.get_runtime_schema.assert_called_once_with(
+        {"account": "test"}, {"database": "mydb"}
+    )
+
+
+def test_runtime_schema_no_body(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime works without a request body."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "snowflake"
+    mock_layer.implementation.configuration = {"account": "test"}
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mock_cls = MagicMock()
+    mock_cls.get_runtime_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+    )
+
+    assert response.status_code == 200
+    mock_cls.get_runtime_schema.assert_called_once_with({"account": "test"}, 
None)
+
+

Review Comment:
   **Suggestion:** These semantic layer configuration and runtime schema tests 
call endpoints that are only registered when the `SEMANTIC_LAYERS` feature flag 
is enabled, but unlike neighboring tests they don't apply the 
`SEMANTIC_LAYERS_APP` parametrization, so the routes may not exist and the 
tests will fail with 404. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Semantic layer type listing tests fail with 404 responses.
   - ❌ Configuration schema tests fail; routes not registered without flag.
   - ❌ Runtime schema tests fail when SEMANTIC_LAYERS disabled by default.
   - ⚠️ Semantic layer type/schema behavior not validated in CI.
   ```
   </details>
   
   ```suggestion
   @SEMANTIC_LAYERS_APP
   def test_get_types(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test GET /types returns registered semantic layer types."""
       mock_cls = MagicMock()
       mock_cls.name = "Snowflake Semantic Layer"
       mock_cls.description = "Connect to Snowflake."
   
       mocker.patch.dict(
           "superset.semantic_layers.api.registry",
           {"snowflake": mock_cls},
           clear=True,
       )
   
       response = client.get("/api/v1/semantic_layer/types")
   
       assert response.status_code == 200
       result = response.json["result"]
       assert len(result) == 1
       assert result[0] == {
           "id": "snowflake",
           "name": "Snowflake Semantic Layer",
           "description": "Connect to Snowflake.",
       }
   
   
   @SEMANTIC_LAYERS_APP
   def test_get_types_empty(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test GET /types returns empty list when no types registered."""
       mocker.patch.dict(
           "superset.semantic_layers.api.registry",
           {},
           clear=True,
       )
   
       response = client.get("/api/v1/semantic_layer/types")
   
       assert response.status_code == 200
       assert response.json["result"] == []
   
   
   @SEMANTIC_LAYERS_APP
   def test_configuration_schema(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test POST /schema/configuration returns schema without partial 
config."""
       mock_cls = MagicMock()
       mock_cls.get_configuration_schema.return_value = {"type": "object"}
   
       mocker.patch.dict(
           "superset.semantic_layers.api.registry",
           {"snowflake": mock_cls},
           clear=True,
       )
   
       response = client.post(
           "/api/v1/semantic_layer/schema/configuration",
           json={"type": "snowflake"},
       )
   
       assert response.status_code == 200
       assert response.json["result"] == {"type": "object"}
       mock_cls.get_configuration_schema.assert_called_once_with(None)
   
   
   @SEMANTIC_LAYERS_APP
   def test_configuration_schema_with_partial_config(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test POST /schema/configuration enriches schema with partial 
config."""
       mock_instance = MagicMock()
       mock_instance.configuration = {"account": "test"}
   
       mock_cls = MagicMock()
       mock_cls.from_configuration.return_value = mock_instance
       mock_cls.get_configuration_schema.return_value = {
           "type": "object",
           "properties": {"database": {"enum": ["db1", "db2"]}},
       }
   
       mocker.patch.dict(
           "superset.semantic_layers.api.registry",
           {"snowflake": mock_cls},
           clear=True,
       )
   
       response = client.post(
           "/api/v1/semantic_layer/schema/configuration",
           json={"type": "snowflake", "configuration": {"account": "test"}},
       )
   
       assert response.status_code == 200
       mock_cls.get_configuration_schema.assert_called_once_with({"account": 
"test"})
   
   
   @SEMANTIC_LAYERS_APP
   def test_configuration_schema_with_invalid_partial_config(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test /schema/configuration returns schema when partial config 
fails."""
       mock_cls = MagicMock()
       mock_cls.from_configuration.side_effect = ValueError("bad config")
       mock_cls.get_configuration_schema.return_value = {"type": "object"}
   
       mocker.patch.dict(
           "superset.semantic_layers.api.registry",
           {"snowflake": mock_cls},
           clear=True,
       )
   
       response = client.post(
           "/api/v1/semantic_layer/schema/configuration",
           json={"type": "snowflake", "configuration": {"bad": "data"}},
       )
   
       assert response.status_code == 200
       mock_cls.get_configuration_schema.assert_called_once_with(None)
   
   
   @SEMANTIC_LAYERS_APP
   def test_configuration_schema_unknown_type(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test POST /schema/configuration returns 400 for unknown type."""
       mocker.patch.dict(
           "superset.semantic_layers.api.registry",
           {},
           clear=True,
       )
   
       response = client.post(
           "/api/v1/semantic_layer/schema/configuration",
           json={"type": "nonexistent"},
       )
   
       assert response.status_code == 400
   
   
   @SEMANTIC_LAYERS_APP
   def test_runtime_schema(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test POST /<uuid>/schema/runtime returns runtime schema."""
       test_uuid = str(uuid_lib.uuid4())
       mock_layer = MagicMock()
       mock_layer.type = "snowflake"
       mock_layer.implementation.configuration = {"account": "test"}
   
       mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
       mock_dao.find_by_uuid.return_value = mock_layer
   
       mock_cls = MagicMock()
       mock_cls.get_runtime_schema.return_value = {"type": "object"}
   
       mocker.patch.dict(
           "superset.semantic_layers.api.registry",
           {"snowflake": mock_cls},
           clear=True,
       )
   
       response = client.post(
           f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
           json={"runtime_data": {"database": "mydb"}},
       )
   
       assert response.status_code == 200
       assert response.json["result"] == {"type": "object"}
       mock_cls.get_runtime_schema.assert_called_once_with(
           {"account": "test"}, {"database": "mydb"}
       )
   
   
   @SEMANTIC_LAYERS_APP
   def test_runtime_schema_no_body(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test POST /<uuid>/schema/runtime works without a request body."""
       test_uuid = str(uuid_lib.uuid4())
       mock_layer = MagicMock()
       mock_layer.type = "snowflake"
       mock_layer.implementation.configuration = {"account": "test"}
   
       mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
       mock_dao.find_by_uuid.return_value = mock_layer
   
       mock_cls = MagicMock()
       mock_cls.get_runtime_schema.return_value = {"type": "object"}
   
       mocker.patch.dict(
           "superset.semantic_layers.api.registry",
           {"snowflake": mock_cls},
           clear=True,
       )
   
       response = client.post(
           f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
       )
   
       assert response.status_code == 200
       mock_cls.get_runtime_schema.assert_called_once_with({"account": "test"}, 
None)
   
   
   @SEMANTIC_LAYERS_APP
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. The `app` fixture in `tests/unit_tests/conftest.py:76-125` builds a 
Superset app from
   `superset.config` and initializes views via 
`SupersetAppInitializer.init_app()`, which
   ultimately calls `SupersetAppInitializer.init_views()` in
   `superset/initialization/__init__.py:145-227`.
   
   2. Inside `init_views`, semantic layer APIs (`SemanticLayerRestApi`,
   `SemanticViewRestApi`) are only registered when
   `feature_flag_manager.is_feature_enabled("SEMANTIC_LAYERS")` is true (see
   `superset/initialization/__init__.py:269-276`), so when the 
`SEMANTIC_LAYERS` feature flag
   is false or unset the `/api/v1/semantic_layer/...` routes are not added.
   
   3. The tests `test_get_types`, `test_get_types_empty`, 
`test_configuration_schema*`, and
   `test_runtime_schema*` in 
`tests/unit_tests/semantic_layers/api_test.py:275-501` call
   these endpoints using the plain `client` fixture and `full_api_access` 
fixture but do NOT
   apply the `@SEMANTIC_LAYERS_APP` parametrization defined at lines 37-41, so 
the app is
   created with default feature flags where `SEMANTIC_LAYERS` is not forced on.
   
   4. Running these tests with the default test configuration yields 404 
responses for routes
   such as `/api/v1/semantic_layer/types` and 
`/api/v1/semantic_layer/schema/configuration`,
   causing assertions like `assert response.status_code == 200` at lines 293, 
317, 341, 373,
   398, 419, 450, 484, and `assert response.status_code == 404` at line 501 to 
fail because
   they expect successful semantic layer API behavior rather than a 
missing-route 404.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/semantic_layers/api_test.py
   **Line:** 275:487
   **Comment:**
        *Possible Bug: These semantic layer configuration and runtime schema 
tests call endpoints that are only registered when the `SEMANTIC_LAYERS` 
feature flag is enabled, but unlike neighboring tests they don't apply the 
`SEMANTIC_LAYERS_APP` parametrization, so the routes may not exist and the 
tests will fail with 404.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38370&comment_hash=51488e88a8de19b63fe6b752891cbc37abe69eef166acbb9f1148e3426ba0cba&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38370&comment_hash=51488e88a8de19b63fe6b752891cbc37abe69eef166acbb9f1148e3426ba0cba&reaction=dislike'>👎</a>



##########
tests/unit_tests/semantic_layers/api_test.py:
##########
@@ -259,3 +265,624 @@ def test_put_semantic_view_empty_payload(
     )
 
     assert response.status_code == 200
+
+
+# =============================================================================
+# SemanticLayerRestApi tests
+# =============================================================================
+
+
+def test_get_types(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test GET /types returns registered semantic layer types."""
+    mock_cls = MagicMock()
+    mock_cls.name = "Snowflake Semantic Layer"
+    mock_cls.description = "Connect to Snowflake."
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.get("/api/v1/semantic_layer/types")
+
+    assert response.status_code == 200
+    result = response.json["result"]
+    assert len(result) == 1
+    assert result[0] == {
+        "id": "snowflake",
+        "name": "Snowflake Semantic Layer",
+        "description": "Connect to Snowflake.",
+    }
+
+
+def test_get_types_empty(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test GET /types returns empty list when no types registered."""
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {},
+        clear=True,
+    )
+
+    response = client.get("/api/v1/semantic_layer/types")
+
+    assert response.status_code == 200
+    assert response.json["result"] == []
+
+
+def test_configuration_schema(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /schema/configuration returns schema without partial 
config."""
+    mock_cls = MagicMock()
+    mock_cls.get_configuration_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "snowflake"},
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"] == {"type": "object"}
+    mock_cls.get_configuration_schema.assert_called_once_with(None)
+
+
+def test_configuration_schema_with_partial_config(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /schema/configuration enriches schema with partial config."""
+    mock_instance = MagicMock()
+    mock_instance.configuration = {"account": "test"}
+
+    mock_cls = MagicMock()
+    mock_cls.from_configuration.return_value = mock_instance
+    mock_cls.get_configuration_schema.return_value = {
+        "type": "object",
+        "properties": {"database": {"enum": ["db1", "db2"]}},
+    }
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "snowflake", "configuration": {"account": "test"}},
+    )
+
+    assert response.status_code == 200
+    mock_cls.get_configuration_schema.assert_called_once_with({"account": 
"test"})
+
+
+def test_configuration_schema_with_invalid_partial_config(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test /schema/configuration returns schema when partial config fails."""
+    mock_cls = MagicMock()
+    mock_cls.from_configuration.side_effect = ValueError("bad config")
+    mock_cls.get_configuration_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "snowflake", "configuration": {"bad": "data"}},
+    )
+
+    assert response.status_code == 200
+    mock_cls.get_configuration_schema.assert_called_once_with(None)
+
+
+def test_configuration_schema_unknown_type(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /schema/configuration returns 400 for unknown type."""
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {},
+        clear=True,
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/schema/configuration",
+        json={"type": "nonexistent"},
+    )
+
+    assert response.status_code == 400
+
+
+def test_runtime_schema(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime returns runtime schema."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "snowflake"
+    mock_layer.implementation.configuration = {"account": "test"}
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mock_cls = MagicMock()
+    mock_cls.get_runtime_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+        json={"runtime_data": {"database": "mydb"}},
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"] == {"type": "object"}
+    mock_cls.get_runtime_schema.assert_called_once_with(
+        {"account": "test"}, {"database": "mydb"}
+    )
+
+
+def test_runtime_schema_no_body(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime works without a request body."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "snowflake"
+    mock_layer.implementation.configuration = {"account": "test"}
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mock_cls = MagicMock()
+    mock_cls.get_runtime_schema.return_value = {"type": "object"}
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+    )
+
+    assert response.status_code == 200
+    mock_cls.get_runtime_schema.assert_called_once_with({"account": "test"}, 
None)
+
+
+def test_runtime_schema_not_found(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime returns 404 when layer not found."""
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = None
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{uuid_lib.uuid4()}/schema/runtime",
+    )
+
+    assert response.status_code == 404
+
+
+@SEMANTIC_LAYERS_APP
+def test_runtime_schema_unknown_type(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime returns 400 for unknown type."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "unknown_type"
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+    )
+
+    assert response.status_code == 400
+    assert "Unknown type" in response.json["message"]
+
+
+@SEMANTIC_LAYERS_APP
+def test_runtime_schema_exception(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST /<uuid>/schema/runtime returns 400 when schema raises."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_layer = MagicMock()
+    mock_layer.type = "snowflake"
+    mock_layer.implementation.configuration = {"account": "test"}
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = mock_layer
+
+    mock_cls = MagicMock()
+    mock_cls.get_runtime_schema.side_effect = ValueError("Bad config")
+
+    mocker.patch.dict(
+        "superset.semantic_layers.api.registry",
+        {"snowflake": mock_cls},
+        clear=True,
+    )
+
+    response = client.post(
+        f"/api/v1/semantic_layer/{test_uuid}/schema/runtime",
+    )
+
+    assert response.status_code == 400
+    assert "Bad config" in response.json["message"]
+
+
+def test_post_semantic_layer(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST / creates a semantic layer."""
+    test_uuid = uuid_lib.uuid4()
+    new_model = MagicMock()
+    new_model.uuid = test_uuid
+
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.CreateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.return_value = new_model
+
+    payload = {
+        "name": "My Layer",
+        "type": "snowflake",
+        "configuration": {"account": "test"},
+    }
+    response = client.post("/api/v1/semantic_layer/", json=payload)
+
+    assert response.status_code == 201
+    assert response.json["result"]["uuid"] == str(test_uuid)
+    mock_command.assert_called_once_with(payload)
+
+
+def test_post_semantic_layer_invalid(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST / returns 422 when validation fails."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.CreateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = SemanticLayerInvalidError(
+        "Unknown type: bad"
+    )
+
+    payload = {
+        "name": "My Layer",
+        "type": "bad",
+        "configuration": {},
+    }
+    response = client.post("/api/v1/semantic_layer/", json=payload)
+
+    assert response.status_code == 422
+
+
+def test_post_semantic_layer_create_failed(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST / returns 422 when creation fails."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.CreateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = 
SemanticLayerCreateFailedError()
+
+    payload = {
+        "name": "My Layer",
+        "type": "snowflake",
+        "configuration": {"account": "test"},
+    }
+    response = client.post("/api/v1/semantic_layer/", json=payload)
+
+    assert response.status_code == 422
+
+
+def test_post_semantic_layer_missing_required_fields(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test POST / returns 400 when required fields are missing."""
+    mocker.patch(
+        "superset.semantic_layers.api.CreateSemanticLayerCommand",
+    )
+
+    response = client.post(
+        "/api/v1/semantic_layer/",
+        json={"name": "Only name"},
+    )
+
+    assert response.status_code == 400
+
+
+def test_put_semantic_layer(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test PUT /<uuid> updates a semantic layer."""
+    test_uuid = uuid_lib.uuid4()
+    changed_model = MagicMock()
+    changed_model.uuid = test_uuid
+
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.UpdateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.return_value = changed_model
+
+    payload = {"name": "Updated Name"}
+    response = client.put(
+        f"/api/v1/semantic_layer/{test_uuid}",
+        json=payload,
+    )
+
+    assert response.status_code == 200
+    assert response.json["result"]["uuid"] == str(test_uuid)
+    mock_command.assert_called_once_with(str(test_uuid), payload)
+
+
+def test_put_semantic_layer_not_found(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test PUT /<uuid> returns 404 when layer not found."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.UpdateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = SemanticLayerNotFoundError()
+
+    response = client.put(
+        f"/api/v1/semantic_layer/{uuid_lib.uuid4()}",
+        json={"name": "New"},
+    )
+
+    assert response.status_code == 404
+
+
+def test_put_semantic_layer_invalid(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test PUT /<uuid> returns 422 when validation fails."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.UpdateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = SemanticLayerInvalidError(
+        "Name already exists"
+    )
+
+    response = client.put(
+        f"/api/v1/semantic_layer/{uuid_lib.uuid4()}",
+        json={"name": "Duplicate"},
+    )
+
+    assert response.status_code == 422
+
+
+def test_put_semantic_layer_update_failed(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test PUT /<uuid> returns 422 when update fails."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.UpdateSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = 
SemanticLayerUpdateFailedError()
+
+    response = client.put(
+        f"/api/v1/semantic_layer/{uuid_lib.uuid4()}",
+        json={"name": "Test"},
+    )
+
+    assert response.status_code == 422
+
+
+@SEMANTIC_LAYERS_APP
+def test_put_semantic_layer_validation_error(
+    client: Any,
+    full_api_access: None,
+) -> None:
+    """Test PUT /<uuid> returns 400 when payload fails schema validation."""
+    response = client.put(
+        f"/api/v1/semantic_layer/{uuid_lib.uuid4()}",
+        json={"cache_timeout": "not_a_number"},
+    )
+
+    assert response.status_code == 400
+
+
+def test_delete_semantic_layer(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test DELETE /<uuid> deletes a semantic layer."""
+    test_uuid = str(uuid_lib.uuid4())
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.DeleteSemanticLayerCommand",
+    )
+    mock_command.return_value.run.return_value = None
+
+    response = client.delete(f"/api/v1/semantic_layer/{test_uuid}")
+
+    assert response.status_code == 200
+    mock_command.assert_called_once_with(test_uuid)
+
+
+def test_delete_semantic_layer_not_found(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test DELETE /<uuid> returns 404 when layer not found."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.DeleteSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = SemanticLayerNotFoundError()
+
+    response = client.delete(f"/api/v1/semantic_layer/{uuid_lib.uuid4()}")
+
+    assert response.status_code == 404
+
+
+def test_delete_semantic_layer_failed(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test DELETE /<uuid> returns 422 when deletion fails."""
+    mock_command = mocker.patch(
+        "superset.semantic_layers.api.DeleteSemanticLayerCommand",
+    )
+    mock_command.return_value.run.side_effect = 
SemanticLayerDeleteFailedError()
+
+    response = client.delete(f"/api/v1/semantic_layer/{uuid_lib.uuid4()}")
+
+    assert response.status_code == 422
+
+
+def test_get_list_semantic_layers(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test GET / returns list of semantic layers."""
+    layer1 = MagicMock()
+    layer1.uuid = uuid_lib.uuid4()
+    layer1.name = "Layer 1"
+    layer1.description = "First"
+    layer1.type = "snowflake"
+    layer1.cache_timeout = None
+
+    layer2 = MagicMock()
+    layer2.uuid = uuid_lib.uuid4()
+    layer2.name = "Layer 2"
+    layer2.description = None
+    layer2.type = "snowflake"
+    layer2.cache_timeout = 300
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_all.return_value = [layer1, layer2]
+
+    response = client.get("/api/v1/semantic_layer/")
+
+    assert response.status_code == 200
+    result = response.json["result"]
+    assert len(result) == 2
+    assert result[0]["name"] == "Layer 1"
+    assert result[1]["name"] == "Layer 2"
+    assert result[1]["cache_timeout"] == 300
+
+
+def test_get_list_semantic_layers_empty(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test GET / returns empty list when no layers exist."""
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_all.return_value = []
+
+    response = client.get("/api/v1/semantic_layer/")
+
+    assert response.status_code == 200
+    assert response.json["result"] == []
+
+
+def test_get_semantic_layer(
+    client: Any,
+    full_api_access: None,
+    mocker: MockerFixture,
+) -> None:
+    """Test GET /<uuid> returns a single semantic layer."""
+    test_uuid = uuid_lib.uuid4()
+    layer = MagicMock()
+    layer.uuid = test_uuid
+    layer.name = "My Layer"
+    layer.description = "A layer"
+    layer.type = "snowflake"
+    layer.cache_timeout = 600
+
+    mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
+    mock_dao.find_by_uuid.return_value = layer
+
+    response = client.get(f"/api/v1/semantic_layer/{test_uuid}")
+
+    assert response.status_code == 200
+    result = response.json["result"]
+    assert result["uuid"] == str(test_uuid)
+    assert result["name"] == "My Layer"
+    assert result["type"] == "snowflake"
+    assert result["cache_timeout"] == 600
+

Review Comment:
   **Suggestion:** The semantic layer delete and fetch tests exercise routes 
that are only added when the `SEMANTIC_LAYERS` feature flag is enabled, but 
these tests don't use the `SEMANTIC_LAYERS_APP` parametrization used elsewhere, 
so in default settings the endpoints may not exist and the tests will fail with 
404 responses. [possible bug]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Delete semantic layer tests may fail with 404 responses.
   - ❌ List/get semantic layer tests can fail due to missing routes.
   - ⚠️ Retrieval/deletion behavior for semantic layers not reliably covered.
   ```
   </details>
   
   ```suggestion
   @SEMANTIC_LAYERS_APP
   def test_delete_semantic_layer(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test DELETE /<uuid> deletes a semantic layer."""
       test_uuid = str(uuid_lib.uuid4())
       mock_command = mocker.patch(
           "superset.semantic_layers.api.DeleteSemanticLayerCommand",
       )
       mock_command.return_value.run.return_value = None
   
       response = client.delete(f"/api/v1/semantic_layer/{test_uuid}")
   
       assert response.status_code == 200
       mock_command.assert_called_once_with(test_uuid)
   
   
   @SEMANTIC_LAYERS_APP
   def test_delete_semantic_layer_not_found(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test DELETE /<uuid> returns 404 when layer not found."""
       mock_command = mocker.patch(
           "superset.semantic_layers.api.DeleteSemanticLayerCommand",
       )
       mock_command.return_value.run.side_effect = SemanticLayerNotFoundError()
   
       response = client.delete(f"/api/v1/semantic_layer/{uuid_lib.uuid4()}")
   
       assert response.status_code == 404
   
   
   @SEMANTIC_LAYERS_APP
   def test_delete_semantic_layer_failed(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test DELETE /<uuid> returns 422 when deletion fails."""
       mock_command = mocker.patch(
           "superset.semantic_layers.api.DeleteSemanticLayerCommand",
       )
       mock_command.return_value.run.side_effect = 
SemanticLayerDeleteFailedError()
   
       response = client.delete(f"/api/v1/semantic_layer/{uuid_lib.uuid4()}")
   
       assert response.status_code == 422
   
   
   @SEMANTIC_LAYERS_APP
   def test_get_list_semantic_layers(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test GET / returns list of semantic layers."""
       layer1 = MagicMock()
       layer1.uuid = uuid_lib.uuid4()
       layer1.name = "Layer 1"
       layer1.description = "First"
       layer1.type = "snowflake"
       layer1.cache_timeout = None
   
       layer2 = MagicMock()
       layer2.uuid = uuid_lib.uuid4()
       layer2.name = "Layer 2"
       layer2.description = None
       layer2.type = "snowflake"
       layer2.cache_timeout = 300
   
       mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
       mock_dao.find_all.return_value = [layer1, layer2]
   
       response = client.get("/api/v1/semantic_layer/")
   
       assert response.status_code == 200
       result = response.json["result"]
       assert len(result) == 2
       assert result[0]["name"] == "Layer 1"
       assert result[1]["name"] == "Layer 2"
       assert result[1]["cache_timeout"] == 300
   
   
   @SEMANTIC_LAYERS_APP
   def test_get_list_semantic_layers_empty(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test GET / returns empty list when no layers exist."""
       mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
       mock_dao.find_all.return_value = []
   
       response = client.get("/api/v1/semantic_layer/")
   
       assert response.status_code == 200
       assert response.json["result"] == []
   
   
   @SEMANTIC_LAYERS_APP
   def test_get_semantic_layer(
       client: Any,
       full_api_access: None,
       mocker: MockerFixture,
   ) -> None:
       """Test GET /<uuid> returns a single semantic layer."""
       test_uuid = uuid_lib.uuid4()
       layer = MagicMock()
       layer.uuid = test_uuid
       layer.name = "My Layer"
       layer.description = "A layer"
       layer.type = "snowflake"
       layer.cache_timeout = 600
   
       mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
       mock_dao.find_by_uuid.return_value = layer
   
       response = client.get(f"/api/v1/semantic_layer/{test_uuid}")
   
       assert response.status_code == 200
       result = response.json["result"]
       assert result["uuid"] == str(test_uuid)
       assert result["name"] == "My Layer"
       assert result["type"] == "snowflake"
       assert result["cache_timeout"] == 600
   
   
   @SEMANTIC_LAYERS_APP
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. The `SupersetAppInitializer.init_views()` method registers REST APIs in
   `superset/initialization/__init__.py:236-281`, and only registers 
`SemanticLayerRestApi`
   (which defines DELETE and GET endpoints in 
`superset/semantic_layers/api.py:401-455,
   457-481`) when `feature_flag_manager.is_feature_enabled("SEMANTIC_LAYERS")` 
is true
   (condition at lines 269-276).
   
   2. The delete and fetch tests in 
`tests/unit_tests/semantic_layers/api_test.py:752-887`
   (`test_delete_semantic_layer*`, `test_get_list_semantic_layers*`,
   `test_get_semantic_layer*`) use `client` and `full_api_access` fixtures but 
do NOT use the
   `@SEMANTIC_LAYERS_APP` parametrization declared at lines 37-41, so the app 
is created with
   default feature flags and may not have `SEMANTIC_LAYERS` enabled.
   
   3. With `SEMANTIC_LAYERS` disabled, routes like `DELETE 
/api/v1/semantic_layer/<uuid>` and
   `GET /api/v1/semantic_layer/` are not registered; issuing
   `client.delete(f"/api/v1/semantic_layer/{test_uuid}")` (line 764) or
   `client.get("/api/v1/semantic_layer/")` (line 825) returns 404, 
contradicting the tests'
   expectations (`assert response.status_code == 200` at lines 766, 827 and 
`assert
   response.status_code == 422`/`404` in the delete tests).
   
   4. This causes the delete/list/get tests to fail whenever the semantic 
layers feature flag
   is off by default in the test configuration, even though the underlying 
implementations in
   `superset/semantic_layers/api.py` correctly handle normal, not-found, and 
error cases when
   the routes are actually registered.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** tests/unit_tests/semantic_layers/api_test.py
   **Line:** 752:875
   **Comment:**
        *Possible Bug: The semantic layer delete and fetch tests exercise 
routes that are only added when the `SEMANTIC_LAYERS` feature flag is enabled, 
but these tests don't use the `SEMANTIC_LAYERS_APP` parametrization used 
elsewhere, so in default settings the endpoints may not exist and the tests 
will fail with 404 responses.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38370&comment_hash=e8b8230b6b2a7e630529c56dab9d09e5bea44de06d93a45657d311dac3fe6f90&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38370&comment_hash=e8b8230b6b2a7e630529c56dab9d09e5bea44de06d93a45657d311dac3fe6f90&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to