"""Fast-DetectGPT: pure estimator maths + the never-raise/abstain contract. No model is loaded anywhere in this module - the estimator is tested on synthetic logits, and the detector paths exercised are the ones that return before any model load. """ import torch from text_detection.detectors.fastdetectgpt import ( FastDetectGPTDetector, sampling_discrepancy_analytic, ) from text_detection.schema import Verdict def test_discrepancy_is_finite_float(): torch.manual_seed(0) logits = torch.randn(1, 8, 50) labels = torch.randint(0, 50, (1, 8)) d = sampling_discrepancy_analytic(logits, labels) assert isinstance(d, float) assert d == d # not NaN def test_confident_correct_tokens_score_higher_than_random(): # A sequence the model is very confident about (peaked logits on the true # token) yields a higher analytic discrepancy than uniform-ish logits. T, V = 10, 40 labels = torch.randint(0, V, (1, T)) peaked = torch.full((1, T, V), -10.0) for t in range(T): peaked[0, t, labels[0, t]] = 10.0 flat = torch.zeros(1, T, V) assert sampling_discrepancy_analytic(peaked, labels) > sampling_discrepancy_analytic(flat, labels) async def test_empty_text_returns_error_result(): det = FastDetectGPTDetector() res = await det.detect_text(" ") assert res.error is not None assert res.confidence == 0.0 async def test_short_text_abstains_without_loading_model(): det = FastDetectGPTDetector() res = await det.detect_text("only five words here now") assert res.detector == "text_fastdetectgpt" assert res.verdict == Verdict.UNCERTAIN assert res.confidence == 0.0 assert res.error is None # abstain is NOT an error async def test_scoring_failure_is_swallowed(monkeypatch): # The never-raise contract: an exploding model must degrade, not propagate. det = FastDetectGPTDetector() def _boom(self, text): raise RuntimeError("model exploded") monkeypatch.setattr(FastDetectGPTDetector, "_score", _boom) res = await det.detect_text(" ".join(["word"] * 40)) assert res.error is not None assert "model exploded" in res.error assert res.confidence == 0.0