nati-log 2.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 1.0
2
+ Name: nati_log
3
+ Version: 2.2.7
4
+ Summary: Librería para interactuar con la API de NatiLog
5
+ Home-page: UNKNOWN
6
+ Author: Natalí
7
+ Author-email: UNKNOWN
8
+ License: UNKNOWN
9
+ Description: UNKNOWN
10
+ Platform: UNKNOWN
@@ -0,0 +1 @@
1
+ from .client import NatiLogClient
@@ -0,0 +1,160 @@
1
+ import requests
2
+ import datetime
3
+
4
+
5
+ class NatiLogClient:
6
+ def __init__(self, api_url, api_url_login, app_id, username, password):
7
+ self.api_url = api_url.rstrip("/") # Saca la barra final si ya la tiene
8
+ self.api_url_login = api_url_login
9
+ self.app_id = app_id
10
+ self.app_estado = True
11
+ self.username = username
12
+ self.password = password
13
+ self.token = None
14
+ self.get_natilog_token()
15
+ self.actualizar_estado_aplicacion()
16
+
17
+ def get_natilog_token(self):
18
+ payload = {"username": self.username, "password": self.password}
19
+ try:
20
+ response = requests.post(self.api_url_login, json=payload, timeout=10)
21
+ response.raise_for_status() # Lanza un error si el código de estado no es 200
22
+ data = response.json()
23
+ self.token = data.get("access") or data.get("token")
24
+ except requests.exceptions.RequestException as e:
25
+ self.token = None
26
+
27
+ def actualizar_estado_aplicacion(self):
28
+ if not self.token: # si no hay token, no se puede verificar el estado
29
+ return
30
+ headers = {"Authorization": f"Bearer {self.token}"} # encabezado de autorización
31
+ try:
32
+ response = requests.get(
33
+ f"{self.api_url}/aplicaciones/{self.app_id}",
34
+ headers=headers,
35
+ timeout=5,
36
+ ) # solicitud para obtener el estado de la aplicación
37
+ if response.status_code == 401:
38
+ self.get_natilog_token() # si el token ha expirado, obtener uno nuevo
39
+ if not self.token:
40
+ return
41
+ headers["Authorization"] = f"Bearer {self.token}"
42
+ response = requests.get(
43
+ f"{self.api_url}/aplicaciones/{self.app_id}",
44
+ headers=headers,
45
+ timeout=5,
46
+ )
47
+ response.raise_for_status() # Lanza un error si la respuesta no es 200 OK
48
+ data = response.json()
49
+ self.app_estado = data.get("estado", True)
50
+ except requests.exceptions.RequestException: # en caso de error, asumir que la app está activa
51
+ self.app_estado = True
52
+ # si la app está inactiva, no se registrarán eventos
53
+
54
+ def registrar_evento(self, tipo_evento, mensaje, datos=None, fecha=None):
55
+ """
56
+ Registra un evento en la API de NatiLog
57
+ Args:
58
+ tipo_evento: str - Tipo de evento (e.g., "critical", "error", "warning", "info")
59
+ mensaje: str - Mensaje del evento
60
+ datos: dict - Datos adicionales del evento (opcional)
61
+ fecha: str - Fecha y hora del evento en formato ISO 8601 (opcional)
62
+ Returns: dict - Respuesta de la API en formato JSON
63
+ Raises: requests.exceptions.RequestException - Si hay un error en la solicitud
64
+ """
65
+ self.actualizar_estado_aplicacion()
66
+ if not self.app_estado:
67
+ return {"detail": "Aplicación inactiva. Evento omitido."}
68
+
69
+ if fecha is None:
70
+ fecha = (
71
+ datetime.datetime.now().isoformat()
72
+ ) # Fecha y hora actual en formato ISO 8601
73
+
74
+ payload = {
75
+ "aplicacion": self.app_id,
76
+ "tipo_evento": tipo_evento,
77
+ "mensaje": mensaje,
78
+ "datos": datos
79
+ or {}, # Datos adicionales, si no hay datos, envía un diccionario vacío
80
+ "fecha": fecha,
81
+ }
82
+
83
+ if not self.token:
84
+ self.get_natilog_token()
85
+
86
+ headers = {"Content-Type": "application/json"}
87
+
88
+ if self.token:
89
+ headers["Authorization"] = f"Bearer {self.token}"
90
+
91
+ response = requests.post(
92
+ f"{self.api_url}/evento/", json=payload, headers=headers, timeout=5
93
+ )
94
+
95
+ if response.status_code == 401:
96
+ self.get_natilog_token()
97
+ if not self.token:
98
+ response.raise_for_status()
99
+ headers["Authorization"] = f"Bearer {self.token}"
100
+ response = requests.post(
101
+ f"{self.api_url}/evento/", json=payload, headers=headers, timeout=5
102
+ )
103
+
104
+ response.raise_for_status() # Lanza un error si la respuesta no es 200 OK
105
+ return response.json() # Devuelve la respuesta en formato JSON
106
+
107
+ def debug(self, mensaje, datos=None, fecha=None):
108
+ """
109
+ Registra un evento de tipo "debug"
110
+ Args:
111
+ mensaje: str - Mensaje del evento
112
+ datos: dict - Datos adicionales del evento (opcional)
113
+ fecha: str - Fecha y hora del evento en formato ISO 8601 (opcional)
114
+ Returns: dict - Respuesta de la API en formato JSON
115
+ """
116
+ return self.registrar_evento("DEBUG", mensaje, datos, fecha)
117
+
118
+ def error(self, mensaje, datos=None, fecha=None):
119
+ """
120
+ Registra un evento de tipo "error"
121
+ Args:
122
+ mensaje: str - Mensaje del evento
123
+ datos: dict - Datos adicionales del evento (opcional)
124
+ fecha: str - Fecha y hora del evento en formato ISO 8601 (opcional)
125
+ Returns: dict - Respuesta de la API en formato JSON
126
+ """
127
+ return self.registrar_evento("ERROR", mensaje, datos, fecha)
128
+
129
+ def warning(self, mensaje, datos=None, fecha=None):
130
+ """
131
+ Registra un evento de tipo "warning"
132
+ Args:
133
+ mensaje: str - Mensaje del evento
134
+ datos: dict - Datos adicionales del evento (opcional)
135
+ fecha: str - Fecha y hora del evento en formato ISO 8601 (opcional)
136
+ Returns: dict - Respuesta de la API en formato JSON
137
+ """
138
+ return self.registrar_evento("WARNING", mensaje, datos, fecha)
139
+
140
+ def info(self, mensaje, datos=None, fecha=None):
141
+ """
142
+ Registra un evento de tipo "info"
143
+ Args:
144
+ mensaje: str - Mensaje del evento
145
+ datos: dict - Datos adicionales del evento (opcional)
146
+ fecha: str - Fecha y hora del evento en formato ISO 8601 (opcional)
147
+ Returns: dict - Respuesta de la API en formato JSON
148
+ """
149
+ return self.registrar_evento("INFO", mensaje, datos, fecha)
150
+
151
+ def critical(self, mensaje, datos=None, fecha=None):
152
+ """
153
+ Registra un evento de tipo "critical"
154
+ Args:
155
+ mensaje: str - Mensaje del evento
156
+ datos: dict - Datos adicionales del evento (opcional)
157
+ fecha: str - Fecha y hora del evento en formato ISO 8601 (opcional)
158
+ Returns: dict - Respuesta de la API en formato JSON
159
+ """
160
+ return self.registrar_evento("CRITICAL", mensaje, datos, fecha)
@@ -0,0 +1,71 @@
1
+ from django.conf import settings
2
+ from .client import NatiLogClient
3
+
4
+
5
+ class NatiLogMiddleware:
6
+ def __init__(self, get_response):
7
+ """
8
+ Middleware para registrar eventos automáticamente en NatiLog.
9
+ Args:
10
+ :param get_response: callable - La función para obtener la respuesta de la vista
11
+ """
12
+
13
+ self.get_response = get_response
14
+ config = getattr(settings, "NATILOG", {})
15
+ self.levels = config.get(
16
+ "EVENT_LEVELS",
17
+ {"DEBUG": True, "INFO": True, "WARNING": True, "ERROR": True, "CRITICAL": True},
18
+ )
19
+ self.natilog = NatiLogClient(
20
+ api_url=config.get("API_URL"),
21
+ api_url_login=config.get("API_URL_LOGIN"),
22
+ app_id=config.get("APP_ID"),
23
+ username=config.get("USERNAME"),
24
+ password=config.get("PASSWORD"),
25
+ )
26
+ print("NatilogClient iniziado en middleware.")
27
+ print(f"NATILOG config: {config}")
28
+
29
+ def __call__(self, request):
30
+ """
31
+ Procesa la solicitud y registra eventos en NatiLog.
32
+ Args:
33
+ :param request: HttpRequest - La solicitud entrante
34
+ :returns: HttpResponse - La respuesta generada por la vista
35
+ """
36
+ response = self.get_response(request)
37
+
38
+ if not self.natilog:
39
+ return response
40
+
41
+ try:
42
+ if self.levels.get("DEBUG", True):
43
+ self.natilog.debug(
44
+ f"Request recibido: {request.method} {request.path}",
45
+ datos={"usuario": getattr(request.user, "username", None)},
46
+ )
47
+
48
+ if 200 <= response.status_code < 300 and self.levels.get("INFO", True):
49
+ self.natilog.info(
50
+ f"Request OK: {request.method} {request.path}",
51
+ datos={"status_code": response.status_code},
52
+ )
53
+ elif 300 <= response.status_code < 400 and self.levels.get("WARNING", True):
54
+ self.natilog.warning(
55
+ f"Redirect: {request.method} {request.path}",
56
+ datos={"status_code": response.status_code},
57
+ )
58
+ elif 400 <= response.status_code < 500 and self.levels.get("ERROR", True):
59
+ self.natilog.error(
60
+ f"Client Error {response.status_code}: {request.method} {request.path}",
61
+ datos={"status_code": response.status_code},
62
+ )
63
+ elif response.status_code >= 500 and self.levels.get("CRITICAL", True):
64
+ self.natilog.critical(
65
+ f"Server Error {response.status_code}: {request.method} {request.path}",
66
+ datos={"status_code": response.status_code},
67
+ )
68
+ except Exception as exc:
69
+ print(f"Error al registrar evento en NatiLog: {exc}")
70
+
71
+ return response
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 1.0
2
+ Name: nati-log
3
+ Version: 2.2.7
4
+ Summary: Librería para interactuar con la API de NatiLog
5
+ Home-page: UNKNOWN
6
+ Author: Natalí
7
+ Author-email: UNKNOWN
8
+ License: UNKNOWN
9
+ Description: UNKNOWN
10
+ Platform: UNKNOWN
@@ -0,0 +1,14 @@
1
+ pyproject.toml
2
+ setup.py
3
+ nati_log/__init__.py
4
+ nati_log/client.py
5
+ nati_log/middleware.py
6
+ nati_log.egg-info/PKG-INFO
7
+ nati_log.egg-info/SOURCES.txt
8
+ nati_log.egg-info/dependency_links.txt
9
+ nati_log.egg-info/requires.txt
10
+ nati_log.egg-info/top_level.txt
11
+ tests/__init__.py
12
+ tests/settings.py
13
+ tests/test_client.py
14
+ tests/test_middleware.py
@@ -0,0 +1 @@
1
+ requests>=2.25.1
@@ -0,0 +1,2 @@
1
+ nati_log
2
+ tests
@@ -0,0 +1,11 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nati_log"
7
+ version = "2.2.7"
8
+ description = "Estadísticas de logs"
9
+ authors = [{name = "Natalí"}]
10
+ requires-python = ">=3.8"
11
+ dependencies = ["requests>=2.25.1"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="nati_log",
5
+ version="2.2.7",
6
+ author="Natalí",
7
+ install_requires=["requests>=2.25.1"],
8
+ packages=find_packages(),
9
+ description="Librería para interactuar con la API de NatiLog",
10
+ )
File without changes
@@ -0,0 +1,3 @@
1
+ SECRET_KEY = "test"
2
+ INSTALLED_APPS = ["django.contrib.contenttypes", "django.contrib.auth", "nati_log"]
3
+ DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}
@@ -0,0 +1,220 @@
1
+ # -*- coding: utf-8 -*-
2
+ import datetime
3
+ import pytest
4
+ import requests
5
+ from nati_log.client import NatiLogClient
6
+
7
+
8
+ @pytest.fixture
9
+ def api_urls():
10
+ return {
11
+ "api": "http://fake-api/api",
12
+ "login": "http://fake-api/api/auth/usuarios/login/",
13
+ "app": "http://fake-api/api/aplicaciones/5",
14
+ "evento": "http://fake-api/api/evento/",
15
+ }
16
+
17
+
18
+ def _build_client(requests_mock, api_urls, app_estado=True, token="token-1"):
19
+ requests_mock.post(
20
+ api_urls["login"],
21
+ json={"access": token},
22
+ status_code=200,
23
+ )
24
+ requests_mock.get(
25
+ api_urls["app"],
26
+ json={"estado": app_estado},
27
+ status_code=200,
28
+ )
29
+ return NatiLogClient(
30
+ api_url=api_urls["api"],
31
+ api_url_login=api_urls["login"],
32
+ app_id=5,
33
+ username="user",
34
+ password="pass",
35
+ )
36
+
37
+
38
+ def test_registrar_evento_success(requests_mock, api_urls):
39
+ client = _build_client(requests_mock, api_urls)
40
+ requests_mock.post(
41
+ api_urls["evento"],
42
+ json={"status": "ok"},
43
+ status_code=201,
44
+ )
45
+
46
+ resp = client.info("Evento exitoso", datos={"key": "value"})
47
+ assert resp == {"status": "ok"}
48
+
49
+ last = requests_mock.last_request
50
+ assert last.headers["Authorization"] == "Bearer token-1"
51
+ assert last.json()["tipo_evento"] == "INFO"
52
+ assert last.json()["datos"]["key"] == "value"
53
+
54
+
55
+ def test_registrar_evento_app_inactiva_omite_envio(requests_mock, api_urls):
56
+ client = _build_client(requests_mock, api_urls, app_estado=False)
57
+
58
+ result = client.info("No debe enviarse")
59
+ assert result == {"detail": "Aplicación inactiva. Evento omitido."}
60
+ assert len(requests_mock.request_history) == 2 # login + estado, sin POST de evento
61
+
62
+
63
+ def test_registrar_evento_reintenta_en_401(requests_mock, api_urls):
64
+ requests_mock.post(
65
+ api_urls["login"],
66
+ [
67
+ {"json": {"access": "token-1"}, "status_code": 200},
68
+ {"json": {"access": "token-2"}, "status_code": 200},
69
+ ],
70
+ )
71
+ requests_mock.get(api_urls["app"], json={"estado": True}, status_code=200)
72
+ requests_mock.post(
73
+ api_urls["evento"],
74
+ [
75
+ {"status_code": 401},
76
+ {"json": {"status": "ok"}, "status_code": 201},
77
+ ],
78
+ )
79
+
80
+ client = NatiLogClient(
81
+ api_url=api_urls["api"],
82
+ api_url_login=api_urls["login"],
83
+ app_id=5,
84
+ username="user",
85
+ password="pass",
86
+ )
87
+ resp = client.error("Debe reintentar")
88
+ assert resp == {"status": "ok"}
89
+
90
+ evento_calls = [req for req in requests_mock.request_history if req.url == api_urls["evento"]]
91
+ assert len(evento_calls) == 2
92
+ assert evento_calls[-1].headers["Authorization"] == "Bearer token-2"
93
+
94
+
95
+ def test_registrar_evento_sin_token_levanta_http_error(requests_mock, api_urls):
96
+ requests_mock.post(api_urls["login"], status_code=401)
97
+ client = NatiLogClient(
98
+ api_url=api_urls["api"],
99
+ api_url_login=api_urls["login"],
100
+ app_id=5,
101
+ username="user",
102
+ password="pass",
103
+ )
104
+
105
+ requests_mock.post(api_urls["evento"], status_code=401)
106
+ with pytest.raises(requests.exceptions.HTTPError):
107
+ client.warning("Debe fallar")
108
+
109
+
110
+ def test_registrar_evento_con_fecha_personalizada(requests_mock, api_urls):
111
+ client = _build_client(requests_mock, api_urls)
112
+ requests_mock.post(
113
+ api_urls["evento"],
114
+ json={"status": "ok"},
115
+ status_code=201,
116
+ )
117
+
118
+ fecha = datetime.datetime(2024, 1, 1, 12, 0, 0).isoformat()
119
+ client.debug("Con fecha", fecha=fecha)
120
+
121
+ last = requests_mock.last_request
122
+ assert last.json()["fecha"] == fecha
123
+ assert last.json()["tipo_evento"] == "DEBUG"
124
+
125
+
126
+ def test_get_token_exception_deja_token_none(requests_mock, api_urls):
127
+ requests_mock.post(
128
+ api_urls["login"],
129
+ exc=requests.exceptions.ConnectTimeout,
130
+ )
131
+
132
+ client = NatiLogClient(
133
+ api_url=api_urls["api"],
134
+ api_url_login=api_urls["login"],
135
+ app_id=5,
136
+ username="user",
137
+ password="pass",
138
+ )
139
+ assert client.token is None
140
+ assert client.app_estado is True
141
+
142
+
143
+ def test_registrar_evento_retry_sin_token_levanta_error(requests_mock, api_urls):
144
+ requests_mock.post(
145
+ api_urls["login"],
146
+ [
147
+ {"json": {"access": "token-1"}, "status_code": 200},
148
+ {"status_code": 500},
149
+ ],
150
+ )
151
+ requests_mock.get(api_urls["app"], json={"estado": True}, status_code=200)
152
+ requests_mock.post(api_urls["evento"], status_code=401)
153
+
154
+ client = NatiLogClient(
155
+ api_url=api_urls["api"],
156
+ api_url_login=api_urls["login"],
157
+ app_id=5,
158
+ username="user",
159
+ password="pass",
160
+ )
161
+
162
+ with pytest.raises(requests.exceptions.HTTPError):
163
+ client.warning("Debe fallar tras reintento sin token")
164
+
165
+
166
+ def test_registrar_evento_critical(requests_mock, api_urls):
167
+ client = _build_client(requests_mock, api_urls)
168
+ requests_mock.post(
169
+ api_urls["evento"],
170
+ json={"status": "ok"},
171
+ status_code=201,
172
+ )
173
+
174
+ client.critical("Falla crítica")
175
+
176
+ last = requests_mock.last_request
177
+ assert last.json()["tipo_evento"] == "CRITICAL"
178
+
179
+
180
+ def test_actualizar_estado_aplicacion_refresca_token(requests_mock, api_urls):
181
+ requests_mock.post(
182
+ api_urls["login"],
183
+ [
184
+ {"json": {"access": "token-1"}, "status_code": 200},
185
+ {"json": {"access": "token-2"}, "status_code": 200},
186
+ ],
187
+ )
188
+ requests_mock.get(
189
+ api_urls["app"],
190
+ [
191
+ {"status_code": 401},
192
+ {"json": {"estado": False}, "status_code": 200},
193
+ ],
194
+ )
195
+
196
+ client = NatiLogClient(
197
+ api_url=api_urls["api"],
198
+ api_url_login=api_urls["login"],
199
+ app_id=5,
200
+ username="user",
201
+ password="pass",
202
+ )
203
+
204
+ assert client.token == "token-2"
205
+ assert client.estado is False
206
+
207
+
208
+ def test_actualizar_estado_aplicacion_error_mantiene_activa(requests_mock, api_urls):
209
+ requests_mock.post(api_urls["login"], json={"access": "token"}, status_code=200)
210
+ requests_mock.get(api_urls["app"], exc=requests.exceptions.ReadTimeout)
211
+
212
+ client = NatiLogClient(
213
+ api_url=api_urls["api"],
214
+ api_url_login=api_urls["login"],
215
+ app_id=5,
216
+ username="user",
217
+ password="pass",
218
+ )
219
+
220
+ assert client.estado is True
@@ -0,0 +1,192 @@
1
+ # -*- coding: utf-8 -*-
2
+ try:
3
+ from types import SimpleNamespace
4
+ except ImportError:
5
+ class SimpleNamespace(object):
6
+ def __init__(self, **kwargs):
7
+ for key, value in kwargs.items():
8
+ setattr(self, key, value)
9
+
10
+ import pytest
11
+ from django.http import HttpResponse
12
+ from django.test import RequestFactory
13
+ from nati_log.middleware import NatiLogMiddleware
14
+
15
+
16
+ class DummyClient(object):
17
+ def __init__(self):
18
+ self.calls = []
19
+
20
+ def debug(self, mensaje, datos=None, fecha=None):
21
+ self.calls.append(("debug", mensaje, datos))
22
+
23
+ def info(self, mensaje, datos=None, fecha=None):
24
+ self.calls.append(("info", mensaje, datos))
25
+
26
+ def warning(self, mensaje, datos=None, fecha=None):
27
+ self.calls.append(("warning", mensaje, datos))
28
+
29
+ def error(self, mensaje, datos=None, fecha=None):
30
+ self.calls.append(("error", mensaje, datos))
31
+
32
+ def critical(self, mensaje, datos=None, fecha=None):
33
+ self.calls.append(("critical", mensaje, datos))
34
+
35
+
36
+ @pytest.fixture
37
+ def rf():
38
+ return RequestFactory()
39
+
40
+
41
+ def make_response(status=200):
42
+ resp = HttpResponse("ok")
43
+ resp.status_code = status
44
+ return resp
45
+
46
+
47
+ def test_middleware_registra_info_por_defecto(settings, monkeypatch, rf):
48
+ settings.NATILOG = {
49
+ "API_URL": "http://fake/api",
50
+ "API_URL_LOGIN": "http://fake/api/auth/",
51
+ "APP_ID": 1,
52
+ "USERNAME": "user",
53
+ "PASSWORD": "pass",
54
+ "EVENT_LEVELS": {
55
+ "DEBUG": True,
56
+ "INFO": True,
57
+ "WARNING": True,
58
+ "ERROR": True,
59
+ "CRITICAL": True,
60
+ },
61
+ }
62
+ dummy = DummyClient()
63
+ monkeypatch.setattr("nati_log.middleware.NatiLogClient", lambda **_: dummy)
64
+
65
+ middleware = NatiLogMiddleware(lambda req: make_response(200))
66
+ request = rf.get("/path")
67
+ request.user = SimpleNamespace(username="tester")
68
+
69
+ middleware(request)
70
+
71
+ assert ("debug", "Request recibido: GET /path", {"usuario": "tester"}) in dummy.calls
72
+ assert any(call[0] == "info" for call in dummy.calls)
73
+
74
+
75
+ def test_middleware_filtra_niveles(settings, monkeypatch, rf):
76
+ settings.NATILOG = {
77
+ "API_URL": "http://fake/api",
78
+ "API_URL_LOGIN": "http://fake/api/auth/",
79
+ "APP_ID": 1,
80
+ "USERNAME": "user",
81
+ "PASSWORD": "pass",
82
+ "EVENT_LEVELS": {
83
+ "DEBUG": False,
84
+ "INFO": False,
85
+ "WARNING": True,
86
+ "ERROR": True,
87
+ "CRITICAL": True,
88
+ },
89
+ }
90
+ dummy = DummyClient()
91
+ monkeypatch.setattr("nati_log.middleware.NatiLogClient", lambda **_: dummy)
92
+
93
+ middleware = NatiLogMiddleware(lambda req: make_response(302))
94
+ request = rf.get("/redirect")
95
+
96
+ middleware(request)
97
+
98
+ tipos = set(call[0] for call in dummy.calls)
99
+ assert "debug" not in tipos
100
+ assert "info" not in tipos
101
+ assert "warning" in tipos
102
+
103
+
104
+ def test_middleware_registra_error(settings, monkeypatch, rf):
105
+ settings.NATILOG = {
106
+ "API_URL": "http://fake/api",
107
+ "API_URL_LOGIN": "http://fake/api/auth/",
108
+ "APP_ID": 1,
109
+ "USERNAME": "user",
110
+ "PASSWORD": "pass",
111
+ "EVENT_LEVELS": {
112
+ "DEBUG": True,
113
+ "INFO": True,
114
+ "WARNING": True,
115
+ "ERROR": True,
116
+ "CRITICAL": True,
117
+ },
118
+ }
119
+ dummy = DummyClient()
120
+ monkeypatch.setattr("nati_log.middleware.NatiLogClient", lambda **_: dummy)
121
+
122
+ middleware = NatiLogMiddleware(lambda req: make_response(404))
123
+ request = rf.get("/not-found")
124
+ request.user = SimpleNamespace(username="tester")
125
+
126
+ middleware(request)
127
+
128
+ assert any(call[0] == "error" for call in dummy.calls)
129
+
130
+
131
+ def test_middleware_sin_cliente_retorna(settings, monkeypatch, rf):
132
+ settings.NATILOG = {
133
+ "API_URL": "http://fake/api",
134
+ "API_URL_LOGIN": "http://fake/api/auth/",
135
+ "APP_ID": 1,
136
+ "USERNAME": "user",
137
+ "PASSWORD": "pass",
138
+ "EVENT_LEVELS": {"DEBUG": True, "INFO": True, "WARNING": True, "ERROR": True, "CRITICAL": True},
139
+ }
140
+ monkeypatch.setattr("nati_log.middleware.NatiLogClient", lambda **_: None)
141
+
142
+ middleware = NatiLogMiddleware(lambda req: make_response(200))
143
+ request = rf.get("/noop")
144
+ middleware(request)
145
+
146
+
147
+ def test_middleware_registra_critical(settings, monkeypatch, rf):
148
+ settings.NATILOG = {
149
+ "API_URL": "http://fake/api",
150
+ "API_URL_LOGIN": "http://fake/api/auth/",
151
+ "APP_ID": 1,
152
+ "USERNAME": "user",
153
+ "PASSWORD": "pass",
154
+ "EVENT_LEVELS": {"DEBUG": True, "INFO": True, "WARNING": True, "ERROR": True, "CRITICAL": True},
155
+ }
156
+ dummy = DummyClient()
157
+ monkeypatch.setattr("nati_log.middleware.NatiLogClient", lambda **_: dummy)
158
+
159
+ middleware = NatiLogMiddleware(lambda req: make_response(503))
160
+ request = rf.get("/boom")
161
+ request.user = SimpleNamespace(username="tester")
162
+
163
+ middleware(request)
164
+
165
+ assert any(call[0] == "critical" for call in dummy.calls)
166
+
167
+
168
+ def test_middleware_registra_warning(settings, monkeypatch, rf):
169
+ settings.NATILOG = {
170
+ "API_URL": "http://fake/api",
171
+ "API_URL_LOGIN": "http://fake/api/auth/",
172
+ "APP_ID": 1,
173
+ "USERNAME": "user",
174
+ "PASSWORD": "pass",
175
+ "EVENT_LEVELS": {
176
+ "DEBUG": True,
177
+ "INFO": True,
178
+ "WARNING": True,
179
+ "ERROR": True,
180
+ "CRITICAL": True,
181
+ },
182
+ }
183
+ dummy = DummyClient()
184
+ monkeypatch.setattr("nati_log.middleware.NatiLogClient", lambda **_: dummy)
185
+
186
+ middleware = NatiLogMiddleware(lambda req: make_response(302))
187
+ request = rf.get("/redirect")
188
+ request.user = SimpleNamespace(username="tester")
189
+
190
+ middleware(request)
191
+
192
+ assert any(call[0] == "warning" for call in dummy.calls)