diff --git a/src/epint/modules/http_client/__init__.py b/src/epint/modules/http_client/__init__.py index ad06998..85f2763 100644 --- a/src/epint/modules/http_client/__init__.py +++ b/src/epint/modules/http_client/__init__.py @@ -158,7 +158,9 @@ def _check_rate_limit(self, response: Response) -> Optional[float]: def _is_tgt_invalid(self, response: Response) -> bool: """ - 404 hatasında TGT geçersizliğini kontrol et + 404 (CAS: "TGT ... could not be found/is considered invalid") veya 401 + (Şeffaflık/EPYS servisleri: errorCode AUTH009 "Güvenlik bilgisi(TGT) + hatalı!") hatasında TGT geçersizliğini kontrol et. Args: response: HTTP response objesi @@ -166,7 +168,7 @@ def _is_tgt_invalid(self, response: Response) -> bool: Returns: TGT geçersizse True, değilse False """ - if response.status_code != 404: + if response.status_code not in (401, 404): return False try: @@ -174,6 +176,13 @@ def _is_tgt_invalid(self, response: Response) -> bool: response_text = response.text if hasattr(response, 'text') else str(response.content or '') response_text_lower = response_text.lower() + if response.status_code == 401: + # Servis 401 döndüğünde body'de errorCode: AUTH009 var mı kontrol et + # (bkz. şeffaflık örneği: {"errorCode": "AUTH009", "errorMessage": + # "Güvenlik bilgisi(TGT) hatalı!"}) - bu, kullanılan TGT'nin servis + # tarafında (halihazırda) geçersiz sayıldığı anlamına gelir. + return 'auth009' in response_text_lower + # TGT geçersizliğini belirten anahtar kelimeler tgt_invalid_keywords = [ 'could not be found', @@ -247,24 +256,31 @@ def _make_request( try: response = session.request(method=method.upper(), url=url, **kwargs) - # 404 hatası ve TGT geçersizliği kontrolü - if response.status_code == 404 and self._is_tgt_invalid(response): + # 404 (CAS) veya 401 AUTH009 (şeffaflık/EPYS) - TGT geçersizliği kontrolü + if response.status_code in (401, 404) and self._is_tgt_invalid(response): if effective_auth and tgt_retry_count < max_tgt_retries: # TGT geçersiz, ticket'ları temizle logger.warning("TGT geçersiz, ticket cache temizleniyor ve yenileniyor (url=%s)", url) effective_auth.clear_tickets() - # URL'de TGT kodu varsa yeni TGT ile güncelle - if '/cas/v1/tickets/' in url or '/v1/tickets/' in url: - try: - # Yeni TGT al - new_tgt_code, _ = effective_auth.get_tgt() - # URL'deki eski TGT kodunu yeni ile değiştir + try: + # Yeni TGT al + new_tgt_code, _ = effective_auth.get_tgt() + + # URL'de TGT kodu varsa yeni TGT ile güncelle (CAS ST üretimi) + if '/cas/v1/tickets/' in url or '/v1/tickets/' in url: import re # TGT- ile başlayan kodu bul ve değiştir url = re.sub(r'TGT-[^/]+', new_tgt_code, url) - except Exception: - pass + + # Header'da TGT varsa yeni TGT ile güncelle (şeffaflık + # injection-quantity gibi servisler TGT'yi 'TGT' header'ında + # gönderir, URL'de değil - bkz. AUTH009 örneği) + request_headers = kwargs.get('headers') + if request_headers and 'TGT' in request_headers: + request_headers['TGT'] = new_tgt_code + except Exception: + pass tgt_retry_count += 1 retry_count += 1 diff --git a/tests/test_http_client.py b/tests/test_http_client.py index 8d25048..1318764 100644 --- a/tests/test_http_client.py +++ b/tests/test_http_client.py @@ -27,6 +27,35 @@ def test_shared_client_session_persists_across_requests(monkeypatch): assert client._session is not None +def test_401_auth009_clears_tickets_and_refreshes_tgt_header(monkeypatch): + # Regresyon testi: şeffaflık servisleri TGT geçersizliğini 404 değil, + # 401 + {"errorCode": "AUTH009"} ile bildiriyor (bkz. _is_tgt_invalid). + # Bu durumda eskiden hiç fark edilmeyip aynı bayat TGT ile tekrar + # denenip nihayetinde exception fırlatılıyordu; artık ticket cache + # temizlenip yeni TGT alınmalı ve 'TGT' header'ı güncellenmeli. + client = HTTPClient() + + unauthorized = MagicMock(status_code=401, headers={}) + unauthorized.text = '{"errorCode": "AUTH009", "errorMessage": "Güvenlik bilgisi(TGT) hatalı!"}' + ok = MagicMock(status_code=200, headers={}) + ok.raise_for_status = lambda: None + responses = [unauthorized, ok] + + session = MagicMock(request=lambda **kw: responses.pop(0)) + monkeypatch.setattr(client, "_get_session", lambda: session) + + auth = MagicMock() + auth.get_tgt.return_value = ("TGT-NEW", "2099-01-01 00:00:00") + headers = {"TGT": "TGT-STALE"} + + result = client.post("https://seffaflik.epias.com.tr/x", auth=auth, headers=headers) + + assert result is ok + auth.clear_tickets.assert_called_once() + auth.get_tgt.assert_called_once() + assert headers["TGT"] == "TGT-NEW" + + def test_make_request_uses_per_call_auth_not_instance_state(monkeypatch): # Regresyon testi: auth artık client'a instance state olarak yazılmıyor # (paylaşılan client'ta bu, eşzamanlı farklı auth'lu çağrılar arasında