Robustez del flujo de tocar la puerta, tests y CI - #1
Merged
Conversation
Problemas que se corrigen: - connect() lanza asyncio.TimeoutError cuando el bot no tiene permiso de Conectar en el canal destino, y ese caso no se capturaba: el usuario quedaba con "Tocando la puerta de X..." para siempre. Ahora se capturan tambien TimeoutError, OpusNotLoaded y cualquier error inesperado, con un mensaje que distingue falta de permisos de ffmpeg faltante. - El canal elegido no se revalidaba. Como los permisos pueden cambiar mientras el menu esta abierto y el valor llega desde el cliente, el bot podia entrar y anunciar en un canal que el usuario no puede ver. Ademas, un ID de canal de texto llegaba hasta connect() y reventaba con AttributeError. Ahora se verifica que sea un VoiceChannel y que siga en la lista permitida. - La vista de seleccion nunca se detenia tras elegir canal, asi que su on_timeout pisaba el mensaje con "se agoto el tiempo" dos minutos despues aunque la puerta ya se hubiera tocado. - usuarios_en_proceso se filtraba por varios caminos (fallo al responder la interaccion, error antes del try). Como la vista ya estaba detenida, nadie liberaba la llave y el usuario quedaba bloqueado hasta reiniciar el proceso. - El guard era global por usuario en vez de por (guild, usuario), asi que la misma persona colisionaba consigo misma entre servidores. - Solo se puede tener una conexion de voz por servidor: se agrega un lock por guild para que las solicitudes simultaneas hagan fila en vez de fallar. - on_ready se dispara en cada reconexion y limpiaba la carpeta de audios, pudiendo borrar el mp3 que otra solicitud estaba reproduciendo. - El bucle de espera de reproduccion no tenia tope; si ffmpeg se colgaba, el bot se quedaba conectado indefinidamente. - Se cancela la solicitud si el usuario salio del canal Puerta. - El parseo de configuracion reventaba con ValueError si una variable existia pero estaba vacia, con AttributeError si CATEGORY_ID no era una categoria, y con TypeError en edge_tts si faltaba TTS_VOICE. - eliminar_audio_tts solo capturaba PermissionError; cualquier otro OSError se escapaba desde el finally y tapaba el error original. - Se quitan los intents privilegiados message_content y members: no hay comandos de texto ni uso del cache de miembros, y sin ellos el bot arranca sin configuracion extra en el Developer Portal.
dave.py es de Disnake y discord.py no lo usa: el extra [voice] instala davey. PyNaCl tambien lo trae ese extra, con el rango que discord.py necesita. Ademas ninguna dependencia tenia cota superior, asi que una major nueva podia romper la instalacion sin cambiar nada del repo.
63 tests con unittest de la libreria estandar. No necesitan token de Discord, red ni ffmpeg: los objetos de Discord estan reemplazados por dobles de prueba. Los canales falsos heredan de las clases reales de discord.py salteandose su __init__, para que los isinstance() del bot se comporten igual que en produccion. Cubren el parseo de configuracion, los intents, el filtrado de canales, el flujo completo de seleccion (camino feliz, selecciones rechazadas y reporte de errores), el guard de reentrada, el lock por servidor, el ciclo de vida de las vistas y el borrado de audios.
El workflow corre la suite en Python 3.10 a 3.13 en cada push y cada PR contra master. TESTING.md explica como correrla a mano, que cubre cada archivo y como agregar casos nuevos. El README se actualiza en tres puntos que ya no coincidian con el codigo: ya no hay que activar SERVER MEMBERS INTENT, CATEGORY_ID y TTS_VOICE son opcionales, y el filtrado de canales es por visibilidad.
checkout@v4 y setup-python@v5 corren sobre Node 20, que ya esta deprecado en los runners y sale como warning en cada ejecucion.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Qué trae
Arreglos de robustez sobre
main.py, más una suite de tests y un CI que la corre en cada PR. No cambia el diseño ni el flujo de uso del bot.🔴 Los que rompen el bot en uso real
Sin permiso de Conectar, el usuario quedaba colgado.
connect()lanzaasyncio.TimeoutErrorcuando el bot no tiene permiso en el canal destino — Discord simplemente no responde. Ese caso no se capturaba: el mensaje se quedaba en "Tocando la puerta de X..." para siempre y salía un traceback. Ahora se capturan tambiénTimeoutError,OpusNotLoadedy cualquier error inesperado, con mensajes que distinguen falta de permisos de ffmpeg faltante.El canal elegido no se revalidaba. Los permisos pueden cambiar durante los 2 minutos que el menú está abierto, y el valor llega desde el cliente. El bot entraba y anunciaba en canales que el usuario no puede ver. Un ID de canal de texto además llegaba hasta
connect()y reventaba conAttributeError. Ahora se verifica que sea unVoiceChannely que siga en la lista permitida.Los intents privilegiados impedían arrancar. El código pedía
message_content, pero el README solo mandaba a activar SERVER MEMBERS INTENT: siguiendo el README al pie de la letra, el bot no levanta (PrivilegedIntentsRequired). Y ninguno de los dos se usaba — no hay comandos de texto ni lecturas del caché de miembros. Quitándolos, el bot arranca sin configuración extra en el Developer Portal.La vista de selección nunca se detenía. Tras elegir canal seguía viva, así que su
on_timeoutpisaba el mensaje con "Se agotó el tiempo" dos minutos después, aunque la puerta ya se hubiera tocado.usuarios_en_procesose filtraba y bloqueaba al usuario para siempre. Si fallaba la respuesta a la interacción, la vista ya estaba detenida y nadie liberaba la llave: esa persona no podía volver a usar el bot hasta reiniciar el proceso.🟡 Robustez
(guild, usuario)en vez de global, para que la misma persona no colisione consigo misma entre servidores.on_readyes idempotente. Se dispara en cada reconexión, y limpiaba la carpeta de audios — podía borrar el mp3 que otra solicitud estaba reproduciendo.afterdeplay()con timeout.ValueError; unCATEGORY_IDque no fuera categoría, conAttributeError; unTTS_VOICEausente, conTypeErrordentro de edge_tts. Ahora los tres dan un mensaje que dice qué variable está mal, yCATEGORY_ID/TTS_VOICEson opcionales de verdad.eliminar_audio_ttssolo capturabaPermissionError; cualquier otroOSErrorse escapaba desde elfinallyy tapaba el error original.📦 Dependencias
dave.pyes de Disnake y discord.py no lo usa: el extra[voice]instaladavey.PyNaCltambién lo trae ese extra, con el rango que discord.py necesita. Se quitan los dos y se les pone cota superior a las tres restantes, que no tenían.✅ Tests y CI
63 tests con
unittestde la estándar. No necesitan token, red ni ffmpeg. Los canales falsos heredan de las clases reales de discord.py (salteándose su__init__) para que losisinstance()del bot se comporten igual que en producción.Corriendo esta misma suite contra el código de
master, 47 de los 63 fallan. Los dos casos más claros:masterconnect()→AttributeErrorEl workflow corre la suite en Python 3.10–3.13 en cada push y cada PR contra
master. En TESTING.md está cómo correrla a mano y cómo agregar casos.🤔 Para decidir vos
En
canales_disponibles_paraestaba comentado# and not permisos.connect. Lo dejé como estaba (se filtra solo por visibilidad) y ajusté el README, que prometía otra cosa. Si la idea original era no ofrecer canales a los que el usuario ya puede entrar, es descomentar esa línea y cambiar un test.Cómo probarlo
python -m unittest discover -s tests -t .