@@ -242,6 +242,124 @@ def test_explanation_cpp():
242242 d = r .json ()
243243 assert d ["language" ] == "C++"
244244
245+ # ── Cyclomatic Complexity ─────────────────────────────────────────────────────
246+ def test_explanation_cyclomatic_fields_present ():
247+ r = client .post ("/explanation/" , json = {"code" : PYTHON_CLEAN , "language" : "python" })
248+ assert r .status_code == 200
249+ d = r .json ()
250+ assert "cyclomatic_complexity" in d
251+ assert "complexity_risk" in d
252+ assert isinstance (d ["cyclomatic_complexity" ], int )
253+ assert d ["complexity_risk" ] in ("Simple" , "Moderate" , "High" , "Very High" )
254+
255+
256+ def test_explanation_cyclomatic_simple ():
257+ code = "def add(a: int, b: int) -> int:\n return a + b\n "
258+ r = client .post ("/explanation/" , json = {"code" : code , "language" : "python" })
259+ assert r .status_code == 200
260+ d = r .json ()
261+ assert d ["cyclomatic_complexity" ] >= 1
262+ assert d ["complexity_risk" ] == "Simple"
263+
264+
265+ def test_explanation_cyclomatic_moderate ():
266+ code = """
267+ def validate(x, y, z):
268+ if x < 0:
269+ return False
270+ elif y < 0:
271+ return False
272+ elif z < 0:
273+ return False
274+ elif x > 100 or y > 100:
275+ return False
276+ else:
277+ return True
278+ """
279+ r = client .post ("/explanation/" , json = {"code" : code , "language" : "python" })
280+ assert r .status_code == 200
281+ d = r .json ()
282+ assert 6 <= d ["cyclomatic_complexity" ] <= 10
283+ assert d ["complexity_risk" ] == "Moderate"
284+
285+
286+ def test_explanation_cyclomatic_high ():
287+ code = """
288+ def process(items, config, flags):
289+ if not items:
290+ return []
291+ elif not config:
292+ return None
293+ results = []
294+ for item in items:
295+ if item and flags.get("enabled"):
296+ if item > 0 and item < 100:
297+ results.append(item)
298+ elif item >= 100 or item == -1:
299+ results.append(item * 2)
300+ else:
301+ results.append(0)
302+ elif item is None:
303+ pass
304+ else:
305+ while item > 0:
306+ item -= 1
307+ results.append(item)
308+ return results
309+ """
310+ r = client .post ("/explanation/" , json = {"code" : code , "language" : "python" })
311+ assert r .status_code == 200
312+ d = r .json ()
313+ assert 11 <= d ["cyclomatic_complexity" ] <= 20
314+ assert d ["complexity_risk" ] == "High"
315+
316+
317+ def test_explanation_cyclomatic_very_high ():
318+ code = """
319+ def route(req, user, db, cache, logger):
320+ if not req:
321+ return None
322+ elif not user:
323+ return None
324+ elif not db:
325+ return None
326+ if user.role == "admin" and user.active and not user.banned:
327+ if req.method == "GET" or req.method == "HEAD":
328+ if cache.has(req.path) and not req.bypass_cache:
329+ return cache.get(req.path)
330+ else:
331+ result = db.query(req.path)
332+ if result and not result.expired:
333+ cache.set(req.path, result)
334+ return result
335+ elif result and result.expired:
336+ if cache.has_stale(req.path) or logger.warn("stale"):
337+ return cache.get_stale(req.path)
338+ else:
339+ return None
340+ else:
341+ return None
342+ elif req.method == "POST":
343+ if user.can_write and req.body:
344+ for item in req.body:
345+ if item.valid and item.size < 1024:
346+ db.insert(item)
347+ else:
348+ logger.warn("invalid item")
349+ else:
350+ return None
351+ else:
352+ return None
353+ else:
354+ return None
355+ """
356+ r = client .post ("/explanation/" , json = {"code" : code , "language" : "python" })
357+ assert r .status_code == 200
358+ d = r .json ()
359+ assert d ["cyclomatic_complexity" ] >= 21
360+ assert d ["complexity_risk" ] == "Very High"
361+
362+
245363# ── Debugging ─────────────────────────────────────────────────────────────────
246364def test_debug_detects_zero_division ():
247365 r = client .post ("/debugging/" , json = {"code" : "result = a / b" , "language" : "python" })
0 commit comments