diff --git a/modules/features/teamsbot/routeFeatureTeamsbot.py b/modules/features/teamsbot/routeFeatureTeamsbot.py index db222efb..94683cfc 100644 --- a/modules/features/teamsbot/routeFeatureTeamsbot.py +++ b/modules/features/teamsbot/routeFeatureTeamsbot.py @@ -788,7 +788,7 @@ async def testAuth( logger.warning(f"[test-auth] No credentials provided and no system bot in DB") credentialDebug["source"] = "noneFound" - # Forward to browser bot service + # Forward to browser bot service (single all-in-one call — may timeout with many variants) browserBotUrl = effectiveConfig._getEffectiveBrowserBotUrl() if not browserBotUrl: raise HTTPException(status_code=503, detail="Browser Bot URL not configured") @@ -799,9 +799,8 @@ async def testAuth( "botAccountEmail": email, "botAccountPassword": password, } - try: - timeout = aiohttp.ClientTimeout(total=900) + timeout = aiohttp.ClientTimeout(total=210) async with aiohttp.ClientSession(timeout=timeout) as session: async with session.post(f"{browserBotUrl}/api/bot/test-auth", json=payload) as resp: if resp.status == 200: @@ -817,6 +816,120 @@ async def testAuth( raise HTTPException(status_code=503, detail=f"Browser Bot connection failed: {str(e)}") +@router.get("/{instanceId}/test-auth/variants") +@limiter.limit("10/minute") +async def getTestAuthVariants( + request: Request, + instanceId: str, + context: RequestContext = Depends(getRequestContext), +): + """ + Get list of available test variant IDs from the Browser Bot. + Frontend calls this once, then runs each variant individually. + """ + import aiohttp + + _validateInstanceAccess(instanceId, context) + effectiveConfig = _getInstanceConfig(instanceId) + browserBotUrl = effectiveConfig._getEffectiveBrowserBotUrl() + if not browserBotUrl: + raise HTTPException(status_code=503, detail="Browser Bot URL not configured") + + browserBotUrl = browserBotUrl.rstrip("/") + try: + timeout = aiohttp.ClientTimeout(total=30) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.get(f"{browserBotUrl}/api/bot/test-auth/variants") as resp: + if resp.status == 200: + return await resp.json() + else: + errorText = await resp.text() + raise HTTPException(status_code=resp.status, detail=f"Browser Bot error: {errorText}") + except aiohttp.ClientError as e: + logger.error(f"Get variants error: {e}") + raise HTTPException(status_code=503, detail=f"Browser Bot connection failed: {str(e)}") + + +@router.post("/{instanceId}/test-auth/variant") +@limiter.limit("10/minute") +async def testAuthSingleVariant( + request: Request, + instanceId: str, + context: RequestContext = Depends(getRequestContext), +): + """ + Run a single test variant. Frontend calls this once per variant (sequentially). + Each call stays within Azure's 240s timeout. + """ + import aiohttp + + mandateId = _validateInstanceAccess(instanceId, context) + interface = _getInterface(context, instanceId) + effectiveConfig = _getInstanceConfig(instanceId) + + body = await request.json() + variantId = body.get("variantId") + meetingUrl = body.get("meetingUrl") + if not variantId or not meetingUrl: + raise HTTPException(status_code=400, detail="variantId and meetingUrl are required") + + # Load credentials (same logic as testAuth) + email = body.get("botEmail") + password = body.get("botPassword") + credentialDebug = {"mandateId": mandateId, "source": "none"} + + if email and password: + credentialDebug["source"] = "requestBody" + credentialDebug["botEmail"] = email + else: + # Fallback: load from DB + try: + from modules.shared.configuration import decryptValue + systemBot = interface.getActiveSystemBot(mandateId) + if not systemBot: + systemBot = interface.getActiveSystemBot(None) + if systemBot: + credentialDebug["searchStrategy"] = "anyMandate" + if systemBot: + email = systemBot.get("email") + encryptedPwd = systemBot.get("encryptedPassword") + if email and encryptedPwd: + password = decryptValue(encryptedPwd, userId=str(context.user.id), keyName="systemBotPassword") + credentialDebug["source"] = "database" + credentialDebug["botEmail"] = email + credentialDebug["botFound"] = True + except Exception as e: + logger.warning(f"[test-auth-variant] Could not load system bot: {e}") + + browserBotUrl = effectiveConfig._getEffectiveBrowserBotUrl() + if not browserBotUrl: + raise HTTPException(status_code=503, detail="Browser Bot URL not configured") + + browserBotUrl = browserBotUrl.rstrip("/") + payload = { + "variantId": variantId, + "meetingUrl": meetingUrl, + "botAccountEmail": email, + "botAccountPassword": password, + } + + try: + timeout = aiohttp.ClientTimeout(total=180) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.post(f"{browserBotUrl}/api/bot/test-auth/variant", json=payload) as resp: + if resp.status == 200: + result = await resp.json() + result["credentialDebug"] = credentialDebug + return result + else: + errorText = await resp.text() + logger.error(f"[test-auth-variant] {variantId} failed: {resp.status}") + raise HTTPException(status_code=resp.status, detail=f"Browser Bot error: {errorText}") + except aiohttp.ClientError as e: + logger.error(f"[test-auth-variant] {variantId} connection error: {e}") + raise HTTPException(status_code=503, detail=f"Browser Bot connection failed: {str(e)}") + + # ========================================================================= # Browser Bot Communication Endpoints (HTTP Fallback + WebSocket) # =========================================================================