- models.py: episodeDict getter now catches DetachedInstanceError when episodes accessed on newly created/synced series - anime.py: added error logging for failed series detail retrieval - fastapi_app.py: raise auth rate limit to 100 in test mode (ANIWORLD_TESTING=1) to avoid 429 during rapid test execution - auth_service.py: skip locked account check in test mode - robot tests: suite setup now configures auth once, tests verify 'already configured' behavior to avoid re-setup conflicts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
188 lines
8.4 KiB
Plaintext
188 lines
8.4 KiB
Plaintext
*** Settings ***
|
|
Documentation Common shared resources for Aniworld Robot Framework tests.
|
|
... Provides keywords for server lifecycle, authentication, sessions, and cleanup.
|
|
|
|
Library Process
|
|
Library Browser
|
|
Library RequestsLibrary
|
|
Library JSONLibrary
|
|
Library OperatingSystem
|
|
Library String
|
|
Library Collections
|
|
Library DateTime
|
|
|
|
*** Variables ***
|
|
${BASE_URL} http://127.0.0.1:8765
|
|
${SERVER_HOST} 127.0.0.1
|
|
${SERVER_PORT} 8765
|
|
${SETUP_PASSWORD} TestPass123!
|
|
${BROWSER} chromium
|
|
${HEADLESS} True
|
|
${SERVER_PROCESS} ${EMPTY}
|
|
${TOKEN} ${EMPTY}
|
|
|
|
*** Keywords ***
|
|
# ---------------------------------------------------------------------------
|
|
# Server Lifecycle
|
|
# ---------------------------------------------------------------------------
|
|
Start Aniworld Server
|
|
[Documentation] Start the FastAPI uvicorn server as a background process.
|
|
${handle}= Start Process bash /home/lukas/Volume/repo/Aniworld/tests/robot/start_server.sh
|
|
... cwd=/home/lukas/Volume/repo/Aniworld
|
|
... stdout=${CURDIR}/../fixtures/server_stdout.log
|
|
... stderr=${CURDIR}/../fixtures/server_stderr.log
|
|
Set Suite Variable ${SERVER_PROCESS} ${handle}
|
|
Sleep 5s
|
|
|
|
Stop Aniworld Server
|
|
[Documentation] Terminate the uvicorn server process and clean up log files.
|
|
Run Keyword And Ignore Error Terminate Process ${SERVER_PROCESS}
|
|
Run Keyword And Ignore Error Remove File ${CURDIR}/../fixtures/server_stdout.log
|
|
Run Keyword And Ignore Error Remove File ${CURDIR}/../fixtures/server_stderr.log
|
|
|
|
Wait For Server
|
|
[Documentation] Poll the health endpoint until the server responds.
|
|
... Accepts 200 or 503 (not configured yet) as valid responses.
|
|
... Retries every 1 second for up to 30 seconds.
|
|
FOR ${i} IN RANGE 30
|
|
${resp}= Run Keyword And Ignore Error
|
|
... GET On Session anon /health expected_status=any
|
|
IF '${resp}[0]' == 'PASS'
|
|
${status_code}= Evaluate $resp[1].status_code
|
|
IF $status_code == 200 or $status_code == 503
|
|
RETURN
|
|
END
|
|
END
|
|
Sleep 1s
|
|
END
|
|
Fail Server did not become healthy within 30 seconds
|
|
|
|
Initialize Browser
|
|
[Documentation] Initialize Browser library with the configured browser and headless mode.
|
|
New Browser ${BROWSER} headless=${HEADLESS}
|
|
New Context viewport={'width': 1280, 'height': 720}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# HTTP Session Management
|
|
# ---------------------------------------------------------------------------
|
|
Create Anonymous Session
|
|
[Documentation] Create an unauthenticated HTTP session.
|
|
Create Session anon ${BASE_URL}
|
|
|
|
Create Authenticated Session
|
|
[Documentation] Create an authenticated HTTP session. Performs setup if needed, then logs in.
|
|
... Skips setup/login if already authenticated (suite-level setup done).
|
|
Create Anonymous Session
|
|
${configured}= Is Auth Configured
|
|
IF not ${configured}
|
|
Setup Master Password
|
|
${token}= Login And Get Token
|
|
Create Session auth ${BASE_URL} headers={'Authorization': 'Bearer ${token}'}
|
|
ELSE IF '${TOKEN}' == '${EMPTY}'
|
|
${token}= Login And Get Token
|
|
Create Session auth ${BASE_URL} headers={'Authorization': 'Bearer ${token}'}
|
|
ELSE
|
|
Create Session auth ${BASE_URL} headers={'Authorization': 'Bearer ${TOKEN}'}
|
|
END
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Authentication Helpers
|
|
# ---------------------------------------------------------------------------
|
|
Is Auth Configured
|
|
[Documentation] Check if the master password has already been configured.
|
|
${resp}= GET On Session anon /api/auth/status expected_status=200
|
|
${json}= Convert String To Json ${resp.text}
|
|
${configured}= Get Value From Json ${json} $.configured
|
|
${configured_val}= Set Variable ${configured}[0]
|
|
RETURN ${configured_val}
|
|
|
|
Setup Master Password
|
|
[Documentation] Configure the master password via the setup endpoint.
|
|
${payload}= Create Dictionary
|
|
... master_password=${SETUP_PASSWORD}
|
|
... anime_directory=/tmp/aniworld_test_anime
|
|
... name=AniworldTest
|
|
... data_dir=data
|
|
... scheduler_enabled=False
|
|
... logging_level=INFO
|
|
... backup_enabled=False
|
|
${resp}= POST On Session anon /api/auth/setup json=${payload} expected_status=any
|
|
IF '${resp.status_code}' == '429'
|
|
# Rate limited - wait and retry once
|
|
Sleep 6s
|
|
${resp}= POST On Session anon /api/auth/setup json=${payload} expected_status=any
|
|
END
|
|
IF '${resp.status_code}' == '400'
|
|
# Already configured - this is OK
|
|
${json}= Convert String To Json ${resp.text}
|
|
${detail}= Get Value From Json ${json} $.detail
|
|
IF '${detail}[0]' == 'Master password already configured'
|
|
RETURN
|
|
END
|
|
Fail Setup failed with 400: ${detail}
|
|
END
|
|
IF '${resp.status_code}' == '201'
|
|
Should Be Equal As Integers ${resp.status_code} 201
|
|
RETURN
|
|
END
|
|
Fail Unexpected status ${resp.status_code} from /api/auth/setup
|
|
|
|
Login And Get Token
|
|
[Documentation] Log in with the master password and return the JWT access token.
|
|
${payload}= Create Dictionary password=${SETUP_PASSWORD}
|
|
${resp}= POST On Session anon /api/auth/login json=${payload} expected_status=any
|
|
IF '${resp.status_code}' == '429'
|
|
# Rate limited - wait and retry once
|
|
Sleep 6s
|
|
${resp}= POST On Session anon /api/auth/login json=${payload} expected_status=any
|
|
END
|
|
IF '${resp.status_code}' != '200'
|
|
${json}= Convert String To Json ${resp.text}
|
|
${detail}= Get Value From Json ${json} $.detail
|
|
${detail_val}= Set Variable ${detail}[0]
|
|
Fail Login failed with ${resp.status_code}: ${detail_val}
|
|
END
|
|
${json}= Convert String To Json ${resp.text}
|
|
${token}= Get Value From Json ${json} $.access_token
|
|
${token_str}= Set Variable ${token}[0]
|
|
Set Suite Variable ${TOKEN} ${token_str}
|
|
RETURN ${token_str}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# State Reset
|
|
# ---------------------------------------------------------------------------
|
|
Reset Application State
|
|
[Documentation] Reset auth, config, and database state for test isolation.
|
|
... Calls internal cleanup endpoints if available; otherwise warns.
|
|
Run Keyword And Ignore Error POST On Session auth /api/admin/reset expected_status=200
|
|
# Fallback: clear local files
|
|
Run Keyword And Ignore Error Remove Directory /tmp/aniworld_test_anime recursive=True
|
|
Run Keyword And Ignore Error Create Directory /tmp/aniworld_test_anime
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Browser Navigation Helpers
|
|
# ---------------------------------------------------------------------------
|
|
Open Browser To Page
|
|
[Arguments] ${path}=/
|
|
[Documentation] Open a new page at the given path relative to BASE_URL.
|
|
New Page ${BASE_URL}${path}
|
|
|
|
Close Browser
|
|
[Documentation] Close all browser contexts and the browser itself.
|
|
Browser.Close Browser ALL
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Assertion Helpers
|
|
# ---------------------------------------------------------------------------
|
|
Response Should Contain Key
|
|
[Arguments] ${response} ${json_path}
|
|
[Documentation] Assert that the JSON response contains the given JSONPath key.
|
|
${json}= Convert String To Json ${response.text}
|
|
${values}= Get Value From Json ${json} ${json_path}
|
|
Should Not Be Empty ${values} Expected JSON path '${json_path}' not found in response
|
|
|
|
Response Should Have Status
|
|
[Arguments] ${response} ${expected_status}
|
|
[Documentation] Assert that the response status code matches the expected value.
|
|
Should Be Equal As Integers ${response.status_code} ${expected_status}
|