Compare commits

...

3 Commits

Author SHA1 Message Date
Zamil Majdy
a3d9c5af7e fix(backend): tighten ValueError message and test assertion in _generate_schema
- Add stable prefix to ValueError: "Invalid graph schema input: {e}"
  to aid debugging and triage
- Add match="name" to pytest.raises to guard the specific missing-name
  failure path, not just any ValueError
2026-04-09 00:08:23 +07:00
Zamil Majdy
50e4039cdd Merge branch 'dev' into spare/16, resolve conflicts in graph_test.py
Keep both the new _generate_schema test and the dev-added get_graph /
validate_graph_execution_permissions test suite. Update imports to
include both GraphModel and validate_graph_execution_permissions.
2026-04-08 23:46:09 +07:00
Zamil Majdy
677b987e62 fix(backend): convert AttributeError to ValueError in _generate_schema
When an AgentInputBlock node is saved without a `name` field, `model_construct`
skips validation and creates an Input object missing the attribute.  The dict
comprehension in `_generate_schema` then raises `AttributeError` at `p.name`,
which falls through to the generic `Exception` → 500 handler in rest_api.py.

Wrap the return block in `try/except AttributeError` and re-raise as
`ValueError` so the existing `ValueError` → 400 handler fires instead,
returning a proper client-error response.

Adds a unit test that would have caught this.
2026-04-08 23:28:50 +07:00
2 changed files with 42 additions and 20 deletions

View File

@@ -333,26 +333,29 @@ class BaseGraph(GraphBaseMeta):
except Exception as e:
logger.error(f"Invalid {type_class}: {input_default}, {e}")
return {
"type": "object",
"properties": {
p.name: {
**{
k: v
for k, v in p.generate_schema().items()
if k not in ["description", "default"]
},
"secret": p.secret,
# Default value has to be set for advanced fields.
"advanced": p.advanced and p.value is not None,
"title": p.title or p.name,
**({"description": p.description} if p.description else {}),
**({"default": p.value} if p.value is not None else {}),
}
for p in schema_fields
},
"required": [p.name for p in schema_fields if p.value is None],
}
try:
return {
"type": "object",
"properties": {
p.name: {
**{
k: v
for k, v in p.generate_schema().items()
if k not in ["description", "default"]
},
"secret": p.secret,
# Default value has to be set for advanced fields.
"advanced": p.advanced and p.value is not None,
"title": p.title or p.name,
**({"description": p.description} if p.description else {}),
**({"default": p.value} if p.value is not None else {}),
}
for p in schema_fields
},
"required": [p.name for p in schema_fields if p.value is None],
}
except AttributeError as e:
raise ValueError(f"Invalid graph schema input: {e}") from e
class GraphTriggerInfo(BaseModel):

View File

@@ -15,6 +15,7 @@ from backend.blocks.basic import StoreValueBlock
from backend.blocks.io import AgentInputBlock, AgentOutputBlock
from backend.data.graph import (
Graph,
GraphModel,
Link,
Node,
get_graph,
@@ -1460,3 +1461,21 @@ async def test_validate_graph_execution_permissions_library_wrong_version_denied
mock_is_published.assert_awaited_once_with(graph_id, graph_version)
lib_where = mock_lib_prisma.return_value.find_first.call_args.kwargs["where"]
assert lib_where["agentGraphVersion"] == graph_version
# ============================================================================
# Tests for _generate_schema AttributeError → ValueError conversion
# ============================================================================
def test_generate_schema_raises_value_error_when_name_missing():
"""AgentInputBlock.Input constructed without 'name' should raise ValueError.
model_construct() skips validation, so the Input object is created without
a 'name' attribute. The dict comprehension in _generate_schema then hits an
AttributeError when it accesses p.name. That AttributeError must be caught
and re-raised as ValueError so the existing 400 handler in rest_api.py fires
instead of falling through to the 500 catch-all.
"""
with pytest.raises(ValueError, match="name"):
GraphModel._generate_schema((AgentInputBlock.Input, {}))