Skip to content

Commit 05bbefc

Browse files
authored
Test JSON serialization of CodeTF v3 (#1056)
1 parent fb5529e commit 05bbefc

File tree

2 files changed

+200
-1
lines changed

2 files changed

+200
-1
lines changed

src/codemodder/codetf/v3/codetf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def validate_description(self):
6161
return self
6262

6363

64-
class Strategy(Enum):
64+
class Strategy(str, Enum):
6565
ai = "ai"
6666
hybrid = "hybrid"
6767
deterministic = "deterministic"
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import json
2+
3+
from codemodder.codetf.common import Change, DiffSide, Finding, FixQuality, Rating, Rule
4+
from codemodder.codetf.v3.codetf import (
5+
AIMetadata,
6+
ChangeSet,
7+
CodeTF,
8+
FixMetadata,
9+
FixResult,
10+
FixStatus,
11+
FixStatusType,
12+
GenerationMetadata,
13+
Reference,
14+
Run,
15+
Strategy,
16+
)
17+
18+
19+
def test_codetf_v3_is_json_serializable():
20+
"""Test that the CodeTF v3 model can be serialized to JSON."""
21+
# Create a minimal valid CodeTF instance
22+
rule = Rule(id="TEST001", name="Test Rule")
23+
finding = Finding(id="finding-123", rule=rule)
24+
25+
# Create a minimal valid Run instance
26+
run = Run(vendor="test-vendor", tool="test-tool", version="1.0.0")
27+
28+
# Create a minimal FixResult with status=skipped (to avoid validation requirements for fixed)
29+
fix_status = FixStatus(status=FixStatusType.skipped, reason="Test reason")
30+
fix_result = FixResult(finding=finding, fixStatus=fix_status)
31+
32+
# Create a CodeTF instance
33+
codetf = CodeTF(run=run, results=[fix_result])
34+
35+
# Test JSON serialization
36+
json_str = json.dumps(codetf.model_dump())
37+
38+
# Verify that we can parse it back
39+
parsed = json.loads(json_str)
40+
assert isinstance(parsed, dict)
41+
assert "run" in parsed
42+
assert "results" in parsed
43+
assert parsed["run"]["vendor"] == "test-vendor"
44+
assert parsed["run"]["tool"] == "test-tool"
45+
assert parsed["run"]["version"] == "1.0.0"
46+
47+
# Verify that CodeTF can be reconstructed from the JSON
48+
codetf_from_json = CodeTF.model_validate_json(json_str)
49+
assert isinstance(codetf_from_json, CodeTF)
50+
assert codetf_from_json.run.vendor == codetf.run.vendor
51+
assert codetf_from_json.run.tool == codetf.run.tool
52+
assert codetf_from_json.run.version == codetf.run.version
53+
assert len(codetf_from_json.results) == len(codetf.results)
54+
55+
56+
def test_codetf_v3_complex_instance_is_serializable():
57+
"""Test that a more complex CodeTF v3 model can be serialized to JSON."""
58+
# Create a rule and finding
59+
rule = Rule(id="TEST001", name="Test Rule", url="https://example.com/rules/TEST001")
60+
finding = Finding(id="finding-123", rule=rule)
61+
62+
# Create a Run instance with all fields
63+
run = Run(
64+
vendor="test-vendor",
65+
tool="test-tool",
66+
version="1.0.0",
67+
projectMetadata="Test Project",
68+
elapsed=1000,
69+
inputMetadata={"args": ["--fix", "--all"]},
70+
analysisMetadata={"memory": "512MB"},
71+
)
72+
73+
# Create a Change
74+
change = Change(
75+
lineNumber=42,
76+
description="Fixed vulnerability",
77+
diffSide=DiffSide.RIGHT,
78+
properties={"severity": "high"},
79+
)
80+
81+
# Create a ChangeSet
82+
change_set = ChangeSet(
83+
path="/path/to/file.py",
84+
diff="@@ -42,1 +42,1 @@\n-unsafe_code()\n+safe_code()",
85+
changes=[change],
86+
)
87+
88+
# Create fix metadata
89+
reference = Reference(url="https://example.com/vuln/123")
90+
ai_metadata = AIMetadata(
91+
provider="test-provider",
92+
models=["gpt-4"],
93+
total_tokens=100,
94+
completion_tokens=50,
95+
prompt_tokens=50,
96+
)
97+
generation_metadata = GenerationMetadata(
98+
strategy=Strategy.hybrid, ai=ai_metadata, provisional=False
99+
)
100+
fix_metadata = FixMetadata(
101+
id="test-fix",
102+
summary="Fixed security vulnerability",
103+
description="Replaced unsafe code with safe code",
104+
references=[reference],
105+
generation=generation_metadata,
106+
)
107+
108+
# Create quality ratings
109+
fix_quality = FixQuality(
110+
safetyRating=Rating(score=5, description="Very safe"),
111+
effectivenessRating=Rating(score=4, description="Effective"),
112+
cleanlinessRating=Rating(score=5, description="Very clean"),
113+
)
114+
115+
# Create FixStatus for a fixed finding
116+
fix_status = FixStatus(
117+
status=FixStatusType.fixed, details="Successfully fixed the vulnerability"
118+
)
119+
120+
# Create FixResult
121+
fix_result = FixResult(
122+
finding=finding,
123+
fixStatus=fix_status,
124+
changeSets=[change_set],
125+
fixMetadata=fix_metadata,
126+
fixQuality=fix_quality,
127+
reasoningSteps=["Identified unsafe code", "Replaced with safe alternative"],
128+
)
129+
130+
# Create the CodeTF instance
131+
codetf = CodeTF(run=run, results=[fix_result])
132+
133+
# Test JSON serialization
134+
json_str = json.dumps(codetf.model_dump())
135+
136+
# Verify that we can parse it back
137+
parsed = json.loads(json_str)
138+
assert isinstance(parsed, dict)
139+
140+
# Verify that CodeTF can be reconstructed from the JSON
141+
codetf_from_json = CodeTF.model_validate_json(json_str)
142+
assert isinstance(codetf_from_json, CodeTF)
143+
144+
# Verify a few complex fields to ensure proper serialization
145+
assert codetf_from_json.results[0].fixMetadata is not None
146+
assert (
147+
codetf_from_json.results[0].fixMetadata.generation.strategy == Strategy.hybrid
148+
)
149+
assert codetf_from_json.results[0].fixQuality is not None
150+
assert codetf_from_json.results[0].fixQuality.safetyRating.score == 5
151+
assert codetf_from_json.results[0].changeSets[0].changes[0].lineNumber == 42
152+
153+
154+
def test_codetf_v3_exclude_none_values():
155+
"""Test that None values are excluded from the JSON output."""
156+
# Create a minimal valid CodeTF instance
157+
rule = Rule(id="TEST001", name="Test Rule")
158+
finding = Finding(id="finding-123", rule=rule)
159+
160+
# Create a Run with optional fields set to None
161+
run = Run(
162+
vendor="test-vendor",
163+
tool="test-tool",
164+
version="1.0.0",
165+
projectMetadata=None,
166+
elapsed=None,
167+
inputMetadata=None,
168+
analysisMetadata=None,
169+
)
170+
171+
# Create a FixResult with optional fields set to None
172+
fix_status = FixStatus(status=FixStatusType.skipped, reason=None, details=None)
173+
fix_result = FixResult(
174+
finding=finding,
175+
fixStatus=fix_status,
176+
changeSets=[],
177+
fixMetadata=None,
178+
fixQuality=None,
179+
reasoningSteps=None,
180+
)
181+
182+
# Create a CodeTF instance
183+
codetf = CodeTF(run=run, results=[fix_result])
184+
185+
# Serialize with exclude_none=True
186+
json_str = json.dumps(codetf.model_dump(exclude_none=True))
187+
parsed = json.loads(json_str)
188+
189+
# Verify None fields are excluded
190+
assert "projectMetadata" not in parsed["run"]
191+
assert "elapsed" not in parsed["run"]
192+
assert "inputMetadata" not in parsed["run"]
193+
assert "analysisMetadata" not in parsed["run"]
194+
195+
assert "reason" not in parsed["results"][0]["fixStatus"]
196+
assert "details" not in parsed["results"][0]["fixStatus"]
197+
assert "fixMetadata" not in parsed["results"][0]
198+
assert "fixQuality" not in parsed["results"][0]
199+
assert "reasoningSteps" not in parsed["results"][0]

0 commit comments

Comments
 (0)