codeflash-internal/django/aiservice/tests/testgen/test_testgen_javascript.py
2026-01-14 22:15:27 -08:00

294 lines
9.4 KiB
Python

"""Tests for JavaScript test generation module.
Tests the prompt building and validation functions without importing
the full testgen module (to avoid LLM dependencies in tests).
"""
import re
from pathlib import Path
import pytest
# Load prompts directly to avoid importing testgen_javascript.py
current_dir = Path(__file__).parent.parent.parent / "testgen"
JS_PROMPTS_DIR = current_dir / "prompts" / "javascript"
JS_EXECUTE_SYSTEM_PROMPT = (JS_PROMPTS_DIR / "execute_system_prompt.md").read_text()
JS_EXECUTE_USER_PROMPT = (JS_PROMPTS_DIR / "execute_user_prompt.md").read_text()
JS_EXECUTE_ASYNC_SYSTEM_PROMPT = (JS_PROMPTS_DIR / "execute_async_system_prompt.md").read_text()
JS_EXECUTE_ASYNC_USER_PROMPT = (JS_PROMPTS_DIR / "execute_async_user_prompt.md").read_text()
JS_PATTERN = re.compile(r"^```(?:javascript|js|typescript|ts)?\s*\n(.*?)\n```", re.MULTILINE | re.DOTALL)
def _has_test_functions(code: str) -> bool:
"""Check if the code contains Jest test functions."""
test_pattern = r"(?:test|it)\s*\(\s*['\"]"
return bool(re.search(test_pattern, code))
def build_javascript_prompt(
function_name: str,
function_code: str,
module_path: str,
test_framework: str,
is_async: bool,
) -> tuple[list[dict[str, str]], str]:
"""Build the prompt messages for JavaScript test generation."""
if is_async:
system_prompt = JS_EXECUTE_ASYNC_SYSTEM_PROMPT
user_prompt = JS_EXECUTE_ASYNC_USER_PROMPT
posthog_event_suffix = "async-"
else:
system_prompt = JS_EXECUTE_SYSTEM_PROMPT
user_prompt = JS_EXECUTE_USER_PROMPT
posthog_event_suffix = ""
system_message = {
"role": "system",
"content": system_prompt.format(function_name=function_name),
}
user_message = {
"role": "user",
"content": user_prompt.format(
test_framework=test_framework,
function_name=function_name,
function_code=function_code,
module_path=module_path,
package_comment="",
),
}
messages = [system_message, user_message]
return messages, posthog_event_suffix
def parse_and_validate_js_output(response_content: str) -> str:
"""Parse and validate the LLM response for JavaScript code."""
if "```" not in response_content:
raise ValueError("LLM response did not contain a code block.")
pattern_res = JS_PATTERN.search(response_content)
if not pattern_res:
raise ValueError("No JavaScript code block found in the LLM response.")
code = pattern_res.group(1).strip()
if not _has_test_functions(code):
raise ValueError("Generated code does not contain any test functions.")
return code
class TestHasTestFunctions:
"""Tests for detecting Jest test functions in code."""
def test_detects_test_function(self) -> None:
"""Test that test() calls are detected."""
code = """
test('should add numbers', () => {
expect(add(1, 2)).toBe(3);
});
"""
assert _has_test_functions(code) is True
def test_detects_it_function(self) -> None:
"""Test that it() calls are detected."""
code = """
it('should add numbers', () => {
expect(add(1, 2)).toBe(3);
});
"""
assert _has_test_functions(code) is True
def test_detects_test_in_describe(self) -> None:
"""Test that tests inside describe blocks are detected."""
code = """
describe('Calculator', () => {
test('should add', () => {
expect(add(1, 2)).toBe(3);
});
});
"""
assert _has_test_functions(code) is True
def test_no_tests_returns_false(self) -> None:
"""Test that code without tests returns false."""
code = """
function add(a, b) {
return a + b;
}
"""
assert _has_test_functions(code) is False
def test_empty_code_returns_false(self) -> None:
"""Test that empty code returns false."""
assert _has_test_functions("") is False
def test_describe_only_returns_false(self) -> None:
"""Test that describe without tests returns false."""
code = """
describe('Calculator', () => {
// No tests yet
});
"""
assert _has_test_functions(code) is False
class TestParseAndValidateJsOutput:
"""Tests for parsing and validating LLM output."""
def test_extracts_javascript_code(self) -> None:
"""Test extracting code from JavaScript code block."""
response = """Here are the tests:
```javascript
test('should work', () => {
expect(true).toBe(true);
});
```
"""
code = parse_and_validate_js_output(response)
assert "test('should work'" in code
assert "expect(true)" in code
def test_extracts_js_code(self) -> None:
"""Test extracting code from js code block."""
response = """```js
test('basic', () => {
expect(1).toBe(1);
});
```"""
code = parse_and_validate_js_output(response)
assert "test('basic'" in code
def test_raises_on_no_code_block(self) -> None:
"""Test that missing code block raises ValueError."""
response = "This response has no code block."
with pytest.raises(ValueError) as exc_info:
parse_and_validate_js_output(response)
assert "code block" in str(exc_info.value).lower()
def test_raises_on_no_tests(self) -> None:
"""Test that code without tests raises ValueError."""
response = """```javascript
function add(a, b) {
return a + b;
}
```"""
with pytest.raises(ValueError) as exc_info:
parse_and_validate_js_output(response)
assert "test" in str(exc_info.value).lower()
class TestBuildJavaScriptPrompt:
"""Tests for building JavaScript prompts."""
def test_sync_function_prompt(self) -> None:
"""Test building prompt for sync function."""
messages, suffix = build_javascript_prompt(
function_name="fibonacci",
function_code="function fibonacci(n) { return n <= 1 ? n : fibonacci(n-1) + fibonacci(n-2); }",
module_path="./fibonacci",
test_framework="jest",
is_async=False,
)
assert len(messages) == 2
assert messages[0]["role"] == "system"
assert messages[1]["role"] == "user"
assert "fibonacci" in messages[0]["content"]
assert "fibonacci" in messages[1]["content"]
assert suffix == "" # Non-async should have empty suffix
def test_async_function_prompt(self) -> None:
"""Test building prompt for async function."""
messages, suffix = build_javascript_prompt(
function_name="fetchData",
function_code="async function fetchData(url) { return await fetch(url); }",
module_path="./api",
test_framework="jest",
is_async=True,
)
assert len(messages) == 2
assert "async" in messages[0]["content"].lower()
assert suffix == "async-"
def test_prompt_includes_function_code(self) -> None:
"""Test that prompt includes the function source code."""
function_code = "function add(a, b) { return a + b; }"
messages, _ = build_javascript_prompt(
function_name="add",
function_code=function_code,
module_path="./math",
test_framework="jest",
is_async=False,
)
# User message should contain the function code
assert function_code in messages[1]["content"]
def test_prompt_includes_module_path(self) -> None:
"""Test that prompt includes the module path."""
messages, _ = build_javascript_prompt(
function_name="add",
function_code="function add(a, b) { return a + b; }",
module_path="./math/utils",
test_framework="jest",
is_async=False,
)
assert "./math/utils" in messages[1]["content"]
class TestJavaScriptTestGenPromptContent:
"""Tests for JavaScript test generation prompt content."""
def test_system_prompt_mentions_jest(self) -> None:
"""Test that system prompt mentions Jest framework."""
messages, _ = build_javascript_prompt(
function_name="test",
function_code="function test() {}",
module_path="./test",
test_framework="jest",
is_async=False,
)
assert "Jest" in messages[0]["content"] or "jest" in messages[0]["content"].lower()
def test_system_prompt_has_test_categories(self) -> None:
"""Test that system prompt mentions test categories."""
messages, _ = build_javascript_prompt(
function_name="test",
function_code="function test() {}",
module_path="./test",
test_framework="jest",
is_async=False,
)
system_content = messages[0]["content"]
# Should mention different test categories
assert "Basic" in system_content or "basic" in system_content.lower()
assert "Edge" in system_content or "edge" in system_content.lower()
def test_system_prompt_warns_against_mocking(self) -> None:
"""Test that system prompt warns against mocking the function under test."""
messages, _ = build_javascript_prompt(
function_name="test",
function_code="function test() {}",
module_path="./test",
test_framework="jest",
is_async=False,
)
system_content = messages[0]["content"]
# Should warn against mocking
assert "mock" in system_content.lower() or "Mock" in system_content