qagent-pytest 0.1.0__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.
Files changed (139) hide show
  1. qagent_pytest-0.1.0/LICENSE +22 -0
  2. qagent_pytest-0.1.0/PKG-INFO +107 -0
  3. qagent_pytest-0.1.0/README.md +72 -0
  4. qagent_pytest-0.1.0/pyproject.toml +80 -0
  5. qagent_pytest-0.1.0/setup.cfg +4 -0
  6. qagent_pytest-0.1.0/src/qagent/__init__.py +14 -0
  7. qagent_pytest-0.1.0/src/qagent/__main__.py +6 -0
  8. qagent_pytest-0.1.0/src/qagent/agents/__init__.py +13 -0
  9. qagent_pytest-0.1.0/src/qagent/agents/generator.py +190 -0
  10. qagent_pytest-0.1.0/src/qagent/agents/planner.py +647 -0
  11. qagent_pytest-0.1.0/src/qagent/agents/reviewer.py +692 -0
  12. qagent_pytest-0.1.0/src/qagent/almacenamiento/__init__.py +25 -0
  13. qagent_pytest-0.1.0/src/qagent/almacenamiento/disco.py +76 -0
  14. qagent_pytest-0.1.0/src/qagent/almacenamiento/historial.py +80 -0
  15. qagent_pytest-0.1.0/src/qagent/almacenamiento/proyecto.py +699 -0
  16. qagent_pytest-0.1.0/src/qagent/almacenamiento/recientes.py +247 -0
  17. qagent_pytest-0.1.0/src/qagent/analysis/__init__.py +20 -0
  18. qagent_pytest-0.1.0/src/qagent/analysis/__main__.py +7 -0
  19. qagent_pytest-0.1.0/src/qagent/analysis/analyzer.py +313 -0
  20. qagent_pytest-0.1.0/src/qagent/analysis/laundering.py +596 -0
  21. qagent_pytest-0.1.0/src/qagent/analysis/rutas.py +698 -0
  22. qagent_pytest-0.1.0/src/qagent/app/__init__.py +0 -0
  23. qagent_pytest-0.1.0/src/qagent/app/bitacora.py +145 -0
  24. qagent_pytest-0.1.0/src/qagent/app/desktop.py +824 -0
  25. qagent_pytest-0.1.0/src/qagent/app/monitor_eventos.py +123 -0
  26. qagent_pytest-0.1.0/src/qagent/config/__init__.py +45 -0
  27. qagent_pytest-0.1.0/src/qagent/config/auditoria.py +127 -0
  28. qagent_pytest-0.1.0/src/qagent/config/carga.py +620 -0
  29. qagent_pytest-0.1.0/src/qagent/config/config.local.ejemplo.toml +35 -0
  30. qagent_pytest-0.1.0/src/qagent/config/config.toml +51 -0
  31. qagent_pytest-0.1.0/src/qagent/config/entorno.py +174 -0
  32. qagent_pytest-0.1.0/src/qagent/config/roles.py +561 -0
  33. qagent_pytest-0.1.0/src/qagent/contracts/__init__.py +91 -0
  34. qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/generated_test.json +7 -0
  35. qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/planner_contract.endpoint.json +72 -0
  36. qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/planner_contract.v2.json +26 -0
  37. qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/review_result.json +12 -0
  38. qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/run_log.log.json +110 -0
  39. qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/run_log.results.json +105 -0
  40. qagent_pytest-0.1.0/src/qagent/contracts/generated_test.schema.json +31 -0
  41. qagent_pytest-0.1.0/src/qagent/contracts/planner_contract.v2.schema.json +166 -0
  42. qagent_pytest-0.1.0/src/qagent/contracts/review_result.schema.json +89 -0
  43. qagent_pytest-0.1.0/src/qagent/contracts/run_log.schema.json +250 -0
  44. qagent_pytest-0.1.0/src/qagent/frontend/README.md +134 -0
  45. qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/LEEME.md +13 -0
  46. qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/LICENSE-Inter-OFL.txt +93 -0
  47. qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/LICENSE-JetBrainsMono-OFL.txt +93 -0
  48. qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/inter-latin-400-normal.woff2 +0 -0
  49. qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/inter-latin-500-normal.woff2 +0 -0
  50. qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/inter-latin-600-normal.woff2 +0 -0
  51. qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/inter-latin-700-normal.woff2 +0 -0
  52. qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/jetbrains-mono-latin-400-normal.woff2 +0 -0
  53. qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/jetbrains-mono-latin-600-normal.woff2 +0 -0
  54. qagent_pytest-0.1.0/src/qagent/frontend/assets/logo/qagent-icono.svg +46 -0
  55. qagent_pytest-0.1.0/src/qagent/frontend/assets/logo/qagent-logo.svg +43 -0
  56. qagent_pytest-0.1.0/src/qagent/frontend/componentes/barra-corrida/barra-corrida.css +23 -0
  57. qagent_pytest-0.1.0/src/qagent/frontend/componentes/barra-corrida/barra-corrida.js +26 -0
  58. qagent_pytest-0.1.0/src/qagent/frontend/componentes/config-pruebas/config-pruebas.css +40 -0
  59. qagent_pytest-0.1.0/src/qagent/frontend/componentes/config-pruebas/config-pruebas.js +137 -0
  60. qagent_pytest-0.1.0/src/qagent/frontend/componentes/configuracion/configuracion.css +20 -0
  61. qagent_pytest-0.1.0/src/qagent/frontend/componentes/configuracion/configuracion.js +127 -0
  62. qagent_pytest-0.1.0/src/qagent/frontend/componentes/configuracion/modal-api.js +126 -0
  63. qagent_pytest-0.1.0/src/qagent/frontend/componentes/ejecucion/ejecucion.css +93 -0
  64. qagent_pytest-0.1.0/src/qagent/frontend/componentes/ejecucion/ejecucion.js +232 -0
  65. qagent_pytest-0.1.0/src/qagent/frontend/componentes/historial/historial.js +125 -0
  66. qagent_pytest-0.1.0/src/qagent/frontend/componentes/informe/informe.css +73 -0
  67. qagent_pytest-0.1.0/src/qagent/frontend/componentes/informe/informe.js +201 -0
  68. qagent_pytest-0.1.0/src/qagent/frontend/componentes/informe/modal-exportar.css +6 -0
  69. qagent_pytest-0.1.0/src/qagent/frontend/componentes/informe/modal-exportar.js +1072 -0
  70. qagent_pytest-0.1.0/src/qagent/frontend/componentes/inicio/inicio.css +21 -0
  71. qagent_pytest-0.1.0/src/qagent/frontend/componentes/inicio/inicio.js +90 -0
  72. qagent_pytest-0.1.0/src/qagent/frontend/componentes/inicio/modal-abrir.js +101 -0
  73. qagent_pytest-0.1.0/src/qagent/frontend/componentes/inicio/modal-clonar.js +89 -0
  74. qagent_pytest-0.1.0/src/qagent/frontend/componentes/monitor/monitor.css +33 -0
  75. qagent_pytest-0.1.0/src/qagent/frontend/componentes/monitor/monitor.js +223 -0
  76. qagent_pytest-0.1.0/src/qagent/frontend/componentes/sidebar/sidebar.css +25 -0
  77. qagent_pytest-0.1.0/src/qagent/frontend/componentes/sidebar/sidebar.js +130 -0
  78. qagent_pytest-0.1.0/src/qagent/frontend/componentes/vista-previa/vista-previa.css +26 -0
  79. qagent_pytest-0.1.0/src/qagent/frontend/componentes/vista-previa/vista-previa.js +373 -0
  80. qagent_pytest-0.1.0/src/qagent/frontend/css/componentes.css +57 -0
  81. qagent_pytest-0.1.0/src/qagent/frontend/css/fuentes.css +8 -0
  82. qagent_pytest-0.1.0/src/qagent/frontend/css/layout.css +33 -0
  83. qagent_pytest-0.1.0/src/qagent/frontend/css/tokens.css +93 -0
  84. qagent_pytest-0.1.0/src/qagent/frontend/index.html +90 -0
  85. qagent_pytest-0.1.0/src/qagent/frontend/js/cargador.js +12 -0
  86. qagent_pytest-0.1.0/src/qagent/frontend/js/datos-corrida.js +257 -0
  87. qagent_pytest-0.1.0/src/qagent/frontend/js/estado.js +81 -0
  88. qagent_pytest-0.1.0/src/qagent/frontend/js/iconos.js +21 -0
  89. qagent_pytest-0.1.0/src/qagent/frontend/js/main.js +12 -0
  90. qagent_pytest-0.1.0/src/qagent/frontend/js/navegacion.js +22 -0
  91. qagent_pytest-0.1.0/src/qagent/frontend/js/puente-python.js +16 -0
  92. qagent_pytest-0.1.0/src/qagent/frontend/js/utils.js +7 -0
  93. qagent_pytest-0.1.0/src/qagent/llm/__init__.py +36 -0
  94. qagent_pytest-0.1.0/src/qagent/llm/cliente.py +407 -0
  95. qagent_pytest-0.1.0/src/qagent/llm/fabrica.py +91 -0
  96. qagent_pytest-0.1.0/src/qagent/llm/fake.py +311 -0
  97. qagent_pytest-0.1.0/src/qagent/llm/ia_simulada.json +59 -0
  98. qagent_pytest-0.1.0/src/qagent/llm/modelos.py +35 -0
  99. qagent_pytest-0.1.0/src/qagent/llm/precios.py +92 -0
  100. qagent_pytest-0.1.0/src/qagent/llm/proveedores.py +380 -0
  101. qagent_pytest-0.1.0/src/qagent/llm/proveedores.toml +140 -0
  102. qagent_pytest-0.1.0/src/qagent/llm/tracker.py +164 -0
  103. qagent_pytest-0.1.0/src/qagent/llm/validar.py +126 -0
  104. qagent_pytest-0.1.0/src/qagent/orchestrator/__init__.py +42 -0
  105. qagent_pytest-0.1.0/src/qagent/orchestrator/corrida.py +460 -0
  106. qagent_pytest-0.1.0/src/qagent/orchestrator/estados.py +49 -0
  107. qagent_pytest-0.1.0/src/qagent/orchestrator/eventos.py +69 -0
  108. qagent_pytest-0.1.0/src/qagent/orchestrator/orquestador.py +480 -0
  109. qagent_pytest-0.1.0/src/qagent/orchestrator/puertos.py +39 -0
  110. qagent_pytest-0.1.0/src/qagent/orchestrator/regresion.py +311 -0
  111. qagent_pytest-0.1.0/src/qagent/proyectos/__init__.py +7 -0
  112. qagent_pytest-0.1.0/src/qagent/proyectos/clonador.py +283 -0
  113. qagent_pytest-0.1.0/src/qagent/proyectos/proyecto_local.py +111 -0
  114. qagent_pytest-0.1.0/src/qagent/proyectos/vista_previa.py +153 -0
  115. qagent_pytest-0.1.0/src/qagent/sandbox/Dockerfile +24 -0
  116. qagent_pytest-0.1.0/src/qagent/sandbox/__init__.py +46 -0
  117. qagent_pytest-0.1.0/src/qagent/sandbox/cliente.py +26 -0
  118. qagent_pytest-0.1.0/src/qagent/sandbox/ejecucion.py +306 -0
  119. qagent_pytest-0.1.0/src/qagent/sandbox/estado.py +71 -0
  120. qagent_pytest-0.1.0/src/qagent/sandbox/imagen.py +151 -0
  121. qagent_pytest-0.1.0/src/qagent/sandbox/modelos.py +93 -0
  122. qagent_pytest-0.1.0/src/qagent/sandbox/normalizacion.py +65 -0
  123. qagent_pytest-0.1.0/src/qagent/storage/__init__.py +23 -0
  124. qagent_pytest-0.1.0/src/qagent/storage/corrida_demo.py +672 -0
  125. qagent_pytest-0.1.0/src/qagent/storage/exportar_informe.py +1959 -0
  126. qagent_pytest-0.1.0/src/qagent/storage/registro.py +380 -0
  127. qagent_pytest-0.1.0/src/qagent/storage/vista_ejecucion.py +297 -0
  128. qagent_pytest-0.1.0/src/qagent/storage/vista_reporte.py +239 -0
  129. qagent_pytest-0.1.0/src/qagent/usuario.py +20 -0
  130. qagent_pytest-0.1.0/src/qagent_pytest.egg-info/PKG-INFO +107 -0
  131. qagent_pytest-0.1.0/src/qagent_pytest.egg-info/SOURCES.txt +137 -0
  132. qagent_pytest-0.1.0/src/qagent_pytest.egg-info/dependency_links.txt +1 -0
  133. qagent_pytest-0.1.0/src/qagent_pytest.egg-info/entry_points.txt +2 -0
  134. qagent_pytest-0.1.0/src/qagent_pytest.egg-info/requires.txt +15 -0
  135. qagent_pytest-0.1.0/src/qagent_pytest.egg-info/top_level.txt +1 -0
  136. qagent_pytest-0.1.0/tests/test_contracts.py +330 -0
  137. qagent_pytest-0.1.0/tests/test_distribucion.py +71 -0
  138. qagent_pytest-0.1.0/tests/test_laundering.py +590 -0
  139. qagent_pytest-0.1.0/tests/test_recursos_paquete.py +63 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Angel Mauricio Oncoy Patricio, Mateo Castillo Pezo,
4
+ Rodrigo Rodríguez Malca y Adrián Sanchez Vargas (equipo QAgent)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: qagent-pytest
3
+ Version: 0.1.0
4
+ Summary: Sistema multiagente que genera y valida pruebas pytest para código Python 3.10+
5
+ Author: Equipo QAgent - Taller Integrador I (UPAO)
6
+ License-Expression: MIT
7
+ Project-URL: Repositorio, https://github.com/AngelOncoy/QAgent
8
+ Project-URL: Problemas, https://github.com/AngelOncoy/QAgent/issues
9
+ Keywords: pytest,testing,unit tests,llm,multi-agent,test generation
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Win32 (MS Windows)
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: Spanish
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Testing
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pywebview>=5.0.0
24
+ Requires-Dist: docker>=7.1.0
25
+ Requires-Dist: requests>=2.31
26
+ Requires-Dist: jsonschema>=4.18
27
+ Requires-Dist: tomli>=2.0; python_version < "3.11"
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
31
+ Requires-Dist: build>=1.2; extra == "dev"
32
+ Provides-Extra: empaquetar
33
+ Requires-Dist: pyinstaller>=6.10; extra == "empaquetar"
34
+ Dynamic: license-file
35
+
36
+ # QAgent
37
+
38
+ **Genera y valida automáticamente pruebas unitarias (pytest) para tu código Python, con un equipo de tres agentes de IA.**
39
+
40
+ Abres tu proyecto, eliges qué probar y QAgent escribe las pruebas, las ejecuta en un entorno aislado y te entrega un informe con lo que pasó, lo que falló y por qué. No acepta pruebas «arregladas a la fuerza»: si una prueba solo pasa porque se le quitó o relajó una aserción, la rechaza.
41
+
42
+ ![Pantalla de inicio de QAgent](https://raw.githubusercontent.com/AngelOncoy/QAgent/develop/docs/imagenes/qagent-inicio.png)
43
+
44
+ ## Cómo funciona
45
+
46
+ | Agente | Qué hace |
47
+ |---|---|
48
+ | **Planner** | Lee tu código (funciones, firmas, docstrings) y decide qué casos hay que probar. |
49
+ | **Generator** | Escribe la prueba pytest de cada caso. |
50
+ | **Reviewer** | La ejecuta en un contenedor Docker sin internet ni acceso a tus archivos, revisa que las aserciones sean legítimas y pide hasta 3 intentos de corrección. |
51
+
52
+ Por cada prueba registra si pasó, la cobertura de líneas y ramas, cuántos intentos necesitó, los tokens que gastó cada agente y, en las funciones críticas, el Mutation Score.
53
+
54
+ ![Informe de una corrida](https://raw.githubusercontent.com/AngelOncoy/QAgent/develop/docs/imagenes/qagent-informe.png)
55
+
56
+ ## Instalación
57
+
58
+ ### Windows: instalador (recomendado)
59
+
60
+ 1. Descarga `QAgent-<versión>-instalador.exe` desde la [última versión publicada](https://github.com/AngelOncoy/QAgent/releases/latest).
61
+ 2. Ejecútalo. No necesita permisos de administrador ni tener Python instalado.
62
+ 3. Si Windows muestra «Windows protegió tu PC», elige **Más información → Ejecutar de todas formas** (el instalador aún no está firmado digitalmente).
63
+
64
+ ### Con pip (si ya usas Python 3.10 o superior)
65
+
66
+ ```bash
67
+ pip install qagent-pytest
68
+ qagent
69
+ ```
70
+
71
+ En PyPI se llama `qagent-pytest` porque el nombre `qagent` ya lo usaba otro proyecto; el programa, el comando y la marca siguen siendo **QAgent**.
72
+
73
+ ## Antes de la primera corrida
74
+
75
+ - **Docker Desktop**, abierto: las pruebas se ejecutan solo dentro de un contenedor aislado. La primera corrida prepara ese entorno sola (necesita internet y tarda unos minutos, una sola vez). [Descargar Docker Desktop](https://www.docker.com/products/docker-desktop/).
76
+ - **Claves de API** de los modelos de IA: se agregan en la pantalla **Configuración** del programa, una por agente.
77
+ - **Git**, si vas a abrir repositorios por su URL.
78
+
79
+ ¿Quieres probarlo sin claves ni costo? Agrega la línea `QAGENT_FAKE_LLM=1` al archivo `.env` de la carpeta `.qagent` de tu usuario (`C:\Users\<tú>\.qagent\.env`): QAgent usará una IA simulada con respuestas grabadas.
80
+
81
+ Tus claves, tu configuración y la lista de proyectos recientes se guardan en esa carpeta `.qagent` de tu usuario, nunca junto al programa. Desinstalar QAgent no las borra.
82
+
83
+ ## Primeros pasos
84
+
85
+ 1. Abre **QAgent** desde el menú Inicio (o con el comando `qagent`).
86
+ 2. En **Configuración**, agrega la API de cada agente.
87
+ 3. En **Proyectos**, elige **Abrir proyecto** (una carpeta con código Python) o **Abrir repositorio** (una URL de Git).
88
+ 4. Revisa la **Vista previa**, elige el perfil en **Configuración de pruebas** e inicia la corrida.
89
+ 5. Sigue el avance en el **Monitor** y revisa el **Reporte final**; puedes exportarlo.
90
+
91
+ ## Alcance
92
+
93
+ QAgent genera pruebas **unitarias** para código **Python 3.10+** y pruebas de endpoints FastAPI. Integración contra servicios reales, extremo a extremo, carga y estrés quedan fuera de esta versión.
94
+
95
+ ## Para el equipo de desarrollo
96
+
97
+ Cómo preparar el entorno, ejecutar las pruebas, construir el instalador y publicar una versión: [`docs/desarrollo.md`](https://github.com/AngelOncoy/QAgent/blob/develop/docs/desarrollo.md). Las reglas del proyecto están en [`AGENTS.md`](https://github.com/AngelOncoy/QAgent/blob/develop/AGENTS.md) y las decisiones técnicas en [`docs/decisiones/`](https://github.com/AngelOncoy/QAgent/tree/develop/docs/decisiones).
98
+
99
+ ## Equipo
100
+
101
+ Proyecto del curso Taller Integrador I, Ingeniería de Sistemas e Inteligencia Artificial, Universidad Privada Antenor Orrego (UPAO).
102
+
103
+ Castillo Pezo, Mateo · Oncoy Patricio, Angel · Rodríguez Malca, Rodrigo · Sanchez Vargas, Adrián
104
+
105
+ ## Licencia
106
+
107
+ [MIT](https://github.com/AngelOncoy/QAgent/blob/develop/LICENSE).
@@ -0,0 +1,72 @@
1
+ # QAgent
2
+
3
+ **Genera y valida automáticamente pruebas unitarias (pytest) para tu código Python, con un equipo de tres agentes de IA.**
4
+
5
+ Abres tu proyecto, eliges qué probar y QAgent escribe las pruebas, las ejecuta en un entorno aislado y te entrega un informe con lo que pasó, lo que falló y por qué. No acepta pruebas «arregladas a la fuerza»: si una prueba solo pasa porque se le quitó o relajó una aserción, la rechaza.
6
+
7
+ ![Pantalla de inicio de QAgent](https://raw.githubusercontent.com/AngelOncoy/QAgent/develop/docs/imagenes/qagent-inicio.png)
8
+
9
+ ## Cómo funciona
10
+
11
+ | Agente | Qué hace |
12
+ |---|---|
13
+ | **Planner** | Lee tu código (funciones, firmas, docstrings) y decide qué casos hay que probar. |
14
+ | **Generator** | Escribe la prueba pytest de cada caso. |
15
+ | **Reviewer** | La ejecuta en un contenedor Docker sin internet ni acceso a tus archivos, revisa que las aserciones sean legítimas y pide hasta 3 intentos de corrección. |
16
+
17
+ Por cada prueba registra si pasó, la cobertura de líneas y ramas, cuántos intentos necesitó, los tokens que gastó cada agente y, en las funciones críticas, el Mutation Score.
18
+
19
+ ![Informe de una corrida](https://raw.githubusercontent.com/AngelOncoy/QAgent/develop/docs/imagenes/qagent-informe.png)
20
+
21
+ ## Instalación
22
+
23
+ ### Windows: instalador (recomendado)
24
+
25
+ 1. Descarga `QAgent-<versión>-instalador.exe` desde la [última versión publicada](https://github.com/AngelOncoy/QAgent/releases/latest).
26
+ 2. Ejecútalo. No necesita permisos de administrador ni tener Python instalado.
27
+ 3. Si Windows muestra «Windows protegió tu PC», elige **Más información → Ejecutar de todas formas** (el instalador aún no está firmado digitalmente).
28
+
29
+ ### Con pip (si ya usas Python 3.10 o superior)
30
+
31
+ ```bash
32
+ pip install qagent-pytest
33
+ qagent
34
+ ```
35
+
36
+ En PyPI se llama `qagent-pytest` porque el nombre `qagent` ya lo usaba otro proyecto; el programa, el comando y la marca siguen siendo **QAgent**.
37
+
38
+ ## Antes de la primera corrida
39
+
40
+ - **Docker Desktop**, abierto: las pruebas se ejecutan solo dentro de un contenedor aislado. La primera corrida prepara ese entorno sola (necesita internet y tarda unos minutos, una sola vez). [Descargar Docker Desktop](https://www.docker.com/products/docker-desktop/).
41
+ - **Claves de API** de los modelos de IA: se agregan en la pantalla **Configuración** del programa, una por agente.
42
+ - **Git**, si vas a abrir repositorios por su URL.
43
+
44
+ ¿Quieres probarlo sin claves ni costo? Agrega la línea `QAGENT_FAKE_LLM=1` al archivo `.env` de la carpeta `.qagent` de tu usuario (`C:\Users\<tú>\.qagent\.env`): QAgent usará una IA simulada con respuestas grabadas.
45
+
46
+ Tus claves, tu configuración y la lista de proyectos recientes se guardan en esa carpeta `.qagent` de tu usuario, nunca junto al programa. Desinstalar QAgent no las borra.
47
+
48
+ ## Primeros pasos
49
+
50
+ 1. Abre **QAgent** desde el menú Inicio (o con el comando `qagent`).
51
+ 2. En **Configuración**, agrega la API de cada agente.
52
+ 3. En **Proyectos**, elige **Abrir proyecto** (una carpeta con código Python) o **Abrir repositorio** (una URL de Git).
53
+ 4. Revisa la **Vista previa**, elige el perfil en **Configuración de pruebas** e inicia la corrida.
54
+ 5. Sigue el avance en el **Monitor** y revisa el **Reporte final**; puedes exportarlo.
55
+
56
+ ## Alcance
57
+
58
+ QAgent genera pruebas **unitarias** para código **Python 3.10+** y pruebas de endpoints FastAPI. Integración contra servicios reales, extremo a extremo, carga y estrés quedan fuera de esta versión.
59
+
60
+ ## Para el equipo de desarrollo
61
+
62
+ Cómo preparar el entorno, ejecutar las pruebas, construir el instalador y publicar una versión: [`docs/desarrollo.md`](https://github.com/AngelOncoy/QAgent/blob/develop/docs/desarrollo.md). Las reglas del proyecto están en [`AGENTS.md`](https://github.com/AngelOncoy/QAgent/blob/develop/AGENTS.md) y las decisiones técnicas en [`docs/decisiones/`](https://github.com/AngelOncoy/QAgent/tree/develop/docs/decisiones).
63
+
64
+ ## Equipo
65
+
66
+ Proyecto del curso Taller Integrador I, Ingeniería de Sistemas e Inteligencia Artificial, Universidad Privada Antenor Orrego (UPAO).
67
+
68
+ Castillo Pezo, Mateo · Oncoy Patricio, Angel · Rodríguez Malca, Rodrigo · Sanchez Vargas, Adrián
69
+
70
+ ## Licencia
71
+
72
+ [MIT](https://github.com/AngelOncoy/QAgent/blob/develop/LICENSE).
@@ -0,0 +1,80 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"] # 77+: licencia como expresión SPDX (license = "MIT")
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # Nombre en PyPI (`pip install qagent-pytest`): «qagent» ya era de otro proyecto. El
7
+ # producto, el comando `qagent` y el paquete `import qagent` no cambian (ADR-005).
8
+ name = "qagent-pytest"
9
+ version = "0.1.0"
10
+ description = "Sistema multiagente que genera y valida pruebas pytest para código Python 3.10+"
11
+ readme = "README.md"
12
+ requires-python = ">=3.10"
13
+ license = "MIT"
14
+ license-files = ["LICENSE"]
15
+ authors = [{ name = "Equipo QAgent - Taller Integrador I (UPAO)" }]
16
+ keywords = ["pytest", "testing", "unit tests", "llm", "multi-agent", "test generation"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Environment :: Win32 (MS Windows)",
20
+ "Intended Audience :: Developers",
21
+ "Natural Language :: Spanish",
22
+ "Operating System :: Microsoft :: Windows",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Topic :: Software Development :: Testing",
28
+ ]
29
+ dependencies = [
30
+ "pywebview>=5.0.0",
31
+ "docker>=7.1.0",
32
+ "requests>=2.31", # se usa directamente en sandbox/ejecucion.py
33
+ "jsonschema>=4.18",
34
+ "tomli>=2.0; python_version < '3.11'",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=8.0.0",
40
+ "ruff>=0.4.0",
41
+ "build>=1.2", # scripts/verificar_paquete.py construye el wheel y el sdist
42
+ ]
43
+ # Solo para construir el ejecutable de Windows (scripts/construir_windows.py).
44
+ empaquetar = [
45
+ "pyinstaller>=6.10",
46
+ ]
47
+
48
+ [project.urls]
49
+ Repositorio = "https://github.com/AngelOncoy/QAgent"
50
+ Problemas = "https://github.com/AngelOncoy/QAgent/issues"
51
+
52
+ [project.scripts]
53
+ qagent = "qagent.app.desktop:ejecutar"
54
+
55
+ # Producto, comando y paquete se llaman igual: QAgent / `qagent` (antes `pyagent`).
56
+ [tool.setuptools.packages.find]
57
+ where = ["src"]
58
+ # Se descubren también las carpetas sin __init__.py (frontend/, contracts/ejemplos/):
59
+ # así setuptools no avisa de paquetes «ambiguos» y sus archivos se instalan.
60
+
61
+ # Recursos que la app necesita instalada (TA-06): sin ellos `pip install qagent` abre sin interfaz.
62
+ [tool.setuptools.package-data]
63
+ "qagent.frontend" = ["**/*"]
64
+ "qagent.config" = ["config.toml", "config.local.ejemplo.toml"]
65
+ "qagent.contracts" = ["*.schema.json", "ejemplos/*.json"]
66
+ "qagent.sandbox" = ["Dockerfile"]
67
+ "qagent.llm" = ["proveedores.toml", "ia_simulada.json"]
68
+
69
+ [tool.pytest.ini_options]
70
+ # src/ para importar `qagent`; la raíz para importar helpers de las pruebas (`tests.orchestrator.fakes`).
71
+ pythonpath = ["src", "."]
72
+ testpaths = ["tests"]
73
+ markers = [
74
+ 'docker: prueba de integración que necesita Docker Desktop iniciado (omitir con -m "not docker")',
75
+ 'red: prueba de integración que necesita internet (omitir con -m "not red")',
76
+ ]
77
+
78
+ [tool.ruff]
79
+ # bench/ y bench_api/ son bancos con bugs sembrados a propósito: no se reformatea ni se "arregla".
80
+ extend-exclude = ["bench", "bench_api"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,14 @@
1
+ """QAgent: sistema multiagente que genera y valida pruebas pytest para código Python."""
2
+
3
+ from importlib.metadata import PackageNotFoundError
4
+ from importlib.metadata import version as _version
5
+
6
+ #: Nombre del paquete en PyPI (`pip install qagent-pytest`). El producto y el `import`
7
+ #: siguen siendo QAgent / `qagent`; «qagent» en PyPI es de otro proyecto (TA-06).
8
+ DISTRIBUCION = "qagent-pytest"
9
+
10
+ try:
11
+ #: Versión instalada; la única fuente es `version` en pyproject.toml.
12
+ __version__ = _version(DISTRIBUCION)
13
+ except PackageNotFoundError: # ejecutado desde src/ sin instalar el paquete
14
+ __version__ = "0.0.0+desarrollo"
@@ -0,0 +1,6 @@
1
+ """Permite abrir la aplicación de escritorio con ``python -m qagent``."""
2
+
3
+ from qagent.app.desktop import ejecutar
4
+
5
+ if __name__ == "__main__":
6
+ ejecutar()
@@ -0,0 +1,13 @@
1
+ """Agentes reales coordinados por el orquestador de QAgent."""
2
+
3
+ from qagent.agents.generator import ErrorRespuestaGenerator, GeneratorAgent
4
+ from qagent.agents.planner import ErrorRespuestaPlanner, PlannerAgent
5
+ from qagent.agents.reviewer import ReviewerAgent
6
+
7
+ __all__ = [
8
+ "ErrorRespuestaGenerator",
9
+ "ErrorRespuestaPlanner",
10
+ "GeneratorAgent",
11
+ "PlannerAgent",
12
+ "ReviewerAgent",
13
+ ]
@@ -0,0 +1,190 @@
1
+ """Agente Generator: convierte un contrato del Planner en código pytest."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ import json
7
+ from pathlib import PurePosixPath
8
+ from typing import Any
9
+
10
+ from qagent import contracts
11
+ from qagent.llm import ClienteLLM, TokenTracker
12
+ from qagent.llm.fake import MARCA_CONTRATO
13
+
14
+ #: Caracteres de feedback (salida de pytest) que se reenvían al modelo.
15
+ LARGO_MAXIMO_FEEDBACK = 2000
16
+
17
+
18
+ class ErrorRespuestaGenerator(ValueError):
19
+ """El Generator devolvió una respuesta que no puede utilizarse."""
20
+
21
+ def __init__(self, mensaje: str, *, codigo: str = "") -> None:
22
+ super().__init__(mensaje)
23
+ self.codigo = codigo
24
+
25
+
26
+ class GeneratorAgent:
27
+ """Genera el test de un único objetivo a partir de su contrato."""
28
+
29
+ def __init__(
30
+ self,
31
+ cliente: ClienteLLM,
32
+ tracker: TokenTracker,
33
+ *,
34
+ es_simulado: bool = False,
35
+ ) -> None:
36
+ """Inicializa el Generator con su cliente y registro de tokens."""
37
+ self.cliente = cliente
38
+ self.tracker = tracker
39
+ self.es_simulado = es_simulado
40
+
41
+ def generar(
42
+ self,
43
+ contrato: dict[str, Any],
44
+ intento: int,
45
+ feedback: str | None,
46
+ ) -> dict[str, Any]:
47
+ """Genera o corrige un test pytest."""
48
+ prompt = self._crear_prompt(contrato, intento, feedback)
49
+ respuesta = self.cliente.generar(prompt, rol="generator")
50
+
51
+ self.tracker.registrar(
52
+ agente="generator",
53
+ modelo=respuesta.modelo,
54
+ tokens_entrada=respuesta.tokens_entrada,
55
+ tokens_salida=respuesta.tokens_salida,
56
+ costo_usd=respuesta.costo_usd,
57
+ es_simulado=self.es_simulado,
58
+ )
59
+
60
+ codigo = _quitar_cerca_python(respuesta.contenido)
61
+ if not codigo.strip():
62
+ raise ErrorRespuestaGenerator("El Generator devolvió código vacío.")
63
+ try:
64
+ ast.parse(codigo)
65
+ except (SyntaxError, ValueError) as error:
66
+ raise ErrorRespuestaGenerator(
67
+ f"El Generator devolvió código Python inválido: {type(error).__name__}: {error}",
68
+ codigo=codigo,
69
+ ) from error
70
+
71
+ generado = {
72
+ "version": "1",
73
+ "objetivo": contrato["objetivo"],
74
+ "intento": intento,
75
+ "codigo": codigo,
76
+ "casos_cubiertos": [caso["id"] for caso in contrato["casos"]],
77
+ }
78
+ contracts.validar(contracts.GENERATED_TEST, generado)
79
+ return generado
80
+
81
+ @staticmethod
82
+ def _crear_prompt(
83
+ contrato: dict[str, Any],
84
+ intento: int,
85
+ feedback: str | None,
86
+ ) -> str:
87
+ # Una sola línea: más corto (menos tokens) y la IA simulada lo puede leer.
88
+ contrato_json = json.dumps(contrato, ensure_ascii=False)
89
+ correccion = (
90
+ _recortar(feedback)
91
+ if feedback
92
+ else "No existe feedback porque este es el primer intento."
93
+ )
94
+ if contrato.get("tipo") == "endpoint":
95
+ reglas_objetivo = _reglas_endpoint(contrato)
96
+ else:
97
+ importacion = (
98
+ f"from {modulo_importable(contrato['modulo'])} import "
99
+ f"{contrato['objetivo']}"
100
+ )
101
+ reglas_objetivo = f"""
102
+ - Importa el objetivo exactamente así: {importacion}
103
+ - Llama directamente a la función con la entrada de cada caso.
104
+ - Para un caso con "excepcion" usa: with pytest.raises(<Excepcion>).
105
+ """.strip()
106
+ return f"""
107
+ Escribe un archivo pytest completo para el contrato indicado.
108
+
109
+ REGLAS OBLIGATORIAS:
110
+ {reglas_objetivo}
111
+ - Escribe una función test_<id> por cada caso del contrato.
112
+ - Usa exactamente los valores esperados del contrato (pytest.approx para float).
113
+ - No ejecutes el código para descubrir resultados.
114
+ - No elimines, cambies ni debilites las aserciones; sin try/except, skip ni xfail.
115
+ - Simula con mocks las funciones incluidas en llama_a.
116
+ - Devuelve únicamente código Python, sin explicaciones.
117
+ - Este es el intento {intento}.
118
+
119
+ FEEDBACK DEL INTENTO ANTERIOR:
120
+ {correccion}
121
+
122
+ {MARCA_CONTRATO} {contrato_json}
123
+ """.strip()
124
+
125
+
126
+ def _reglas_endpoint(contrato: dict[str, Any]) -> str:
127
+ """Reglas de generación para un endpoint FastAPI descrito por el contrato."""
128
+ modulo = modulo_importable(contrato["modulo"])
129
+ aplicacion = contrato["aplicacion"]
130
+ dependencias = contrato.get("dependencias", [])
131
+ simbolos = [aplicacion]
132
+ for dependencia in dependencias:
133
+ simbolos.extend([dependencia["funcion"], dependencia["reemplazo"]])
134
+ importacion = f"from {modulo} import {', '.join(dict.fromkeys(simbolos))}"
135
+ detalle_overrides = (
136
+ json.dumps(dependencias, ensure_ascii=False)
137
+ if dependencias
138
+ else "[] (este endpoint no declara Depends)"
139
+ )
140
+ return f"""
141
+ - Usa fastapi.testclient.TestClient para realizar una petición HTTP real en memoria.
142
+ - Importa la aplicación y los símbolos de override exactamente así: {importacion}
143
+ - Construye TestClient({aplicacion}, raise_server_exceptions=False).
144
+ - Usa client.request con método, ruta, query, json y headers del contrato.
145
+ - Comprueba siempre response.status_code.
146
+ - Si valor_esperado trae "json", compara response.json() completo.
147
+ - Si trae "json_contiene", comprueba cada clave y valor indicados.
148
+ - Dependencias a reemplazar: {detalle_overrides}
149
+ - Para cada dependencia asigna {aplicacion}.dependency_overrides[funcion] = reemplazo.
150
+ - Si una entrada trae "preparacion", instancia el reemplazo una vez en la fixture,
151
+ ejecuta allí cada método/modelo/argumentos declarado y usa un lambda que devuelva
152
+ esa misma instancia durante el test.
153
+ - Usa una fixture con try/finally: guarda una copia de los overrides anteriores y,
154
+ al terminar cada test, limpia el diccionario y restaura exactamente esa copia.
155
+ - Nunca reemplaces el manejador de la ruta ni modelos Pydantic.
156
+ """.strip()
157
+
158
+
159
+ def modulo_importable(modulo: str) -> str:
160
+ """Ruta del módulo relativa al proyecto -> nombre importable.
161
+
162
+ En el sandbox `PYTHONPATH` es la raíz del proyecto, así que
163
+ `bench/banco_mvp.py` se importa como `bench.banco_mvp`.
164
+ """
165
+ return (
166
+ PurePosixPath(modulo.replace("\\", "/"))
167
+ .with_suffix("")
168
+ .as_posix()
169
+ .replace("/", ".")
170
+ )
171
+
172
+
173
+ def _recortar(texto: str, maximo: int = LARGO_MAXIMO_FEEDBACK) -> str:
174
+ """Deja solo el final del feedback (donde pytest pone el error) para ahorrar tokens."""
175
+ if len(texto) <= maximo:
176
+ return texto
177
+ return "[…recortado…]\n" + texto[-maximo:]
178
+
179
+
180
+ def _quitar_cerca_python(contenido: str) -> str:
181
+ """Elimina una cerca Markdown ```python si el modelo la agregó."""
182
+ texto = contenido.strip()
183
+ if texto.startswith("```"):
184
+ lineas = texto.splitlines()
185
+ if lineas and lineas[0].strip().startswith("```"):
186
+ lineas.pop(0)
187
+ if lineas and lineas[-1].strip() == "```":
188
+ lineas.pop()
189
+ texto = "\n".join(lineas).strip()
190
+ return texto + "\n" if texto else ""