afv-library/skills/agentforce-generate/scripts/generators/apex_test_stub.py

122 lines
3.8 KiB
Python

"""Generate @isTest companion classes for scaffolded Apex InvocableMethod actions."""
from __future__ import annotations
from scripts.generators.apex_stub import _TYPE_MAP, _COMPLEX_TYPE_MAP, generate_apex_meta_xml
# Type-appropriate placeholder values for test data
_TEST_PLACEHOLDERS = {
"String": "'test'",
"Decimal": "1",
"Integer": "1",
"Double": "1.0",
"Boolean": "true",
"Date": "Date.today()",
"Datetime": "Datetime.now()",
"Id": "null",
"Map<String, Object>": "new Map<String, Object>()",
"List<Object>": "new List<Object>()",
"SObject": "new Account()",
}
def _placeholder_for_type(apex_type: str) -> str:
"""Return a type-appropriate placeholder value for test data."""
return _TEST_PLACEHOLDERS.get(apex_type, "null")
def _resolve_apex_type(param: dict) -> str:
"""Resolve a parameter dict to its Apex type string."""
return _COMPLEX_TYPE_MAP.get(
param.get("complex_data_type_name", ""),
_TYPE_MAP.get(param.get("type", "string"), "String"),
)
def generate_apex_test_class(
class_name: str,
inputs: list[dict] | None = None,
outputs: list[dict] | None = None,
is_callout: bool = False,
) -> str:
"""Generate an @isTest companion class for an Apex InvocableMethod stub.
Args:
class_name: The production Apex class name (test class will be {class_name}Test).
inputs: Action input definitions (list of dicts with 'name', 'type' keys).
outputs: Action output definitions (list of dicts with 'name', 'type' keys).
is_callout: If True, include HttpCalloutMock inner class.
Returns:
Apex test class source code string.
"""
inputs = inputs or []
outputs = outputs or []
test_name = f"{class_name}Test"
lines = [
"@isTest",
f"private class {test_name} {{",
]
# HttpCalloutMock inner class for callout actions
if is_callout:
lines.extend([
"",
" private class MockHttpResponse implements HttpCalloutMock {",
" public HttpResponse respond(HttpRequest req) {",
" HttpResponse res = new HttpResponse();",
" res.setHeader('Content-Type', 'application/json');",
" res.setBody('{\"status\": \"ok\"}');",
" res.setStatusCode(200);",
" return res;",
" }",
" }",
])
# Test method
lines.extend([
"",
" @isTest",
" static void testInvoke() {",
])
if is_callout:
lines.append(" Test.setMock(HttpCalloutMock.class, new MockHttpResponse());")
lines.append("")
# Build request
lines.append(f" {class_name}.Request req = new {class_name}.Request();")
for inp in inputs:
apex_type = _resolve_apex_type(inp)
placeholder = _placeholder_for_type(apex_type)
lines.append(f" req.{inp['name']} = {placeholder};")
# Invoke
lines.extend([
"",
f" List<{class_name}.Response> results = {class_name}.invoke(",
f" new List<{class_name}.Request>{{ req }}",
" );",
"",
" System.assertNotEquals(null, results, 'Expected non-null response');",
" System.assertEquals(1, results.size(), 'Expected one response');",
])
# Assert outputs are populated
if outputs:
lines.append("")
lines.append(f" {class_name}.Response resp = results[0];")
for out in outputs:
apex_type = _resolve_apex_type(out)
if apex_type == "String":
lines.append(f" System.assertNotEquals(null, resp.{out['name']}, 'Expected {out['name']} to be set');")
lines.extend([
" }",
"}",
])
return "\n".join(lines) + "\n"