import hashlib from datetime import timedelta import pytest from django.utils import timezone from rest_framework import status from infrasynth.security.altcha.services import ALTCHAService from infrasynth.security.models import ALTCHAChallenge @pytest.fixture def altcha_service(db): return ALTCHAService() def compute_solution(salt: str, number: int) -> str: return hashlib.sha256(f"{salt}{number}".encode()).hexdigest() class TestChallengeCreation: def test_create_challenge_persists(self, altcha_service): challenge = altcha_service.create_challenge() assert challenge["algorithm"] == "SHA-256" assert challenge["difficulty"] == 10000 assert ALTCHAChallenge.objects.filter(challenge_id=challenge["challenge_id"]).exists() def test_create_challenge_fields(self, altcha_service): challenge = altcha_service.create_challenge() assert challenge["salt"] assert challenge["challenge_id"] assert challenge["difficulty"] > 0 def test_challenge_has_expiry(self, altcha_service): altcha_service.create_challenge() saved = ALTCHAChallenge.objects.get() assert saved.expires_at > timezone.now() class TestVerification: def test_verify_correct_solution(self, altcha_service): challenge = altcha_service.create_challenge() number = 5 solution = compute_solution(challenge["salt"], number) assert altcha_service.verify(challenge["challenge_id"], solution, number) is True def test_verify_marks_used(self, altcha_service): challenge = altcha_service.create_challenge() solution = compute_solution(challenge["salt"], 5) assert altcha_service.verify(challenge["challenge_id"], solution, 5) is True saved = ALTCHAChallenge.objects.get(challenge_id=challenge["challenge_id"]) assert saved.is_verified is True def test_replay_rejected(self, altcha_service): challenge = altcha_service.create_challenge() solution = compute_solution(challenge["salt"], 5) assert altcha_service.verify(challenge["challenge_id"], solution, 5) is True assert altcha_service.verify(challenge["challenge_id"], solution, 5) is False def test_wrong_solution_rejected(self, altcha_service): challenge = altcha_service.create_challenge() wrong = hashlib.sha256(b"nope").hexdigest() assert altcha_service.verify(challenge["challenge_id"], wrong, 5) is False def test_unknown_challenge_rejected(self, altcha_service): assert altcha_service.verify("unknown-id", "abc", 5) is False def test_expired_challenge_rejected(self, altcha_service): challenge = altcha_service.create_challenge() ALTCHAChallenge.objects.filter(challenge_id=challenge["challenge_id"]).update( expires_at=timezone.now() - timedelta(minutes=10) ) solution = compute_solution(challenge["salt"], 5) assert altcha_service.verify(challenge["challenge_id"], solution, 5) is False class TestComputeSolution: def test_compute_solution_matches_verify(self, altcha_service): challenge = altcha_service.create_challenge() solution, number = altcha_service.compute_solution(challenge["salt"], challenge["difficulty"]) assert altcha_service.verify(challenge["challenge_id"], solution, number) is True class TestALTCHAViews: def test_challenge_endpoint(self, api_client): resp = api_client.post("/api/auth/altcha/challenge/") assert resp.status_code == status.HTTP_200_OK data = resp.json() assert "challenge_id" in data assert "salt" in data assert "difficulty" in data def test_verify_endpoint_correct(self, api_client): svc = ALTCHAService() challenge = svc.create_challenge() solution = compute_solution(challenge["salt"], 3) resp = api_client.post( "/api/auth/altcha/verify/", { "challenge_id": challenge["challenge_id"], "solution": solution, "number": 3, }, format="json", ) assert resp.status_code == status.HTTP_200_OK assert resp.json() == {"verified": True} def test_verify_endpoint_wrong(self, api_client): svc = ALTCHAService() challenge = svc.create_challenge() resp = api_client.post( "/api/auth/altcha/verify/", { "challenge_id": challenge["challenge_id"], "solution": "deadbeef", "number": 1, }, format="json", ) assert resp.json() == {"verified": False}