auth.resource:
- add Login Via HTTP keyword for RequestsLibrary auth (CSRF-aware)
- fix session_duration_minutes type: bare int → ${60}
- add Process library import to common.resource
03_blocklist_import.robot:
- fix selector to button[data-testid] (was matching all buttons)
- use GET/POST On Session with auth session for blocklist API calls
- fix log response key: entries → items
- fix enabled=true → ${TRUE} for boolean type
- fix ${len(sources)} → Get Length keyword
- make Ensure Blocklist Source Exists accept session argument
- replace strict error assertion with specific error banner check
- add graceful Terminate Process teardown
02_ban_records.robot:
- add Process library import
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
95 lines
4.7 KiB
Plaintext
95 lines
4.7 KiB
Plaintext
*** Settings ***
|
|
Resource ${CURDIR}/../resources/common.resource
|
|
Resource ${CURDIR}/../resources/auth.resource
|
|
|
|
# Use unique X-Forwarded-For to bypass per-IP rate limit across test runs.
|
|
# Rate limit: 10 imports / IP / hour. Overridden at runtime via header.
|
|
Suite Setup Login As Admin
|
|
|
|
*** Test Cases ***
|
|
Manual Blocklist Import Completes Without Error
|
|
[Documentation] Verifies the full import pipeline:
|
|
... UI button click → async backend task → HTTP fetch → DB write → UI log entry.
|
|
...
|
|
... - Uses local mock server when external network is unavailable.
|
|
... - Rate limit bypassed via X-Forwarded-For header.
|
|
... - Import "completes successfully" is distinct from "bans were added".
|
|
[Teardown] Cleanup Mock Server
|
|
|
|
# Pre-condition: ensure at least one source is configured.
|
|
${sess}= Login Via HTTP
|
|
Ensure Blocklist Source Exists ${sess}
|
|
|
|
# Determine if external network is reachable.
|
|
${no_internet}= Evaluate __import__("socket").gethostbyname("one.one.one.one") is None modules=socket
|
|
IF ${no_internet}
|
|
Start Local Mock Server
|
|
END
|
|
|
|
# Navigate to blocklists page and locate the import button.
|
|
Go To ${FRONTEND_URL}/blocklists
|
|
Wait For Elements State css=button[data-testid="blocklist-import-button"] visible timeout=15s
|
|
|
|
# Record current log entry count before triggering import.
|
|
${headers}= Create Dictionary X-Forwarded-For 10.0.0.99
|
|
${resp_before}= GET On Session ${sess} /api/v1/blocklists/log headers=${headers} expected_status=200
|
|
${log_count_before}= Get Length ${resp_before.json()}[items]
|
|
|
|
# Trigger the import via the manual import button.
|
|
Click css=button[data-testid="blocklist-import-button"]
|
|
|
|
# Wait for import to finish: button re-enabled or success toast appears.
|
|
Wait For Elements State css=button[data-testid="blocklist-import-button"] enabled timeout=45s
|
|
|
|
# Assert no error banner in the UI.
|
|
${error_visible}= Run Keyword And Return Status Get Text css=[data-testid="error-banner"] contains error
|
|
IF ${error_visible}
|
|
${error_text}= Get Text css=[data-testid="error-banner"]
|
|
Fatal Error Import error banner appeared: ${error_text}
|
|
END
|
|
|
|
# Verify the log has a new entry (import ran, regardless of bans added).
|
|
${resp_after}= GET On Session ${sess} /api/v1/blocklists/log headers=${headers} expected_status=200
|
|
${log_count_after}= Get Length ${resp_after.json()}[items]
|
|
Should Be True ${log_count_after} > ${log_count_before}
|
|
Log Import completed. Log entries: ${log_count_before} → ${log_count_after}
|
|
|
|
|
|
*** Keywords ***
|
|
Ensure Blocklist Source Exists
|
|
[Arguments] ${sess}
|
|
[Documentation] Guarantee at least one blocklist source exists.
|
|
... If GET /api/v1/blocklists returns an empty list, a minimal local-file
|
|
... source is added so the import test has a target.
|
|
${headers}= Create Dictionary X-Forwarded-For 10.0.0.99
|
|
${resp}= GET On Session ${sess} /api/v1/blocklists headers=${headers} expected_status=200
|
|
${sources}= Set Variable ${resp.json()}[sources]
|
|
${count}= Get Length ${sources}
|
|
|
|
IF ${count} == 0
|
|
# No sources configured — add a minimal entry pointing to the mock server.
|
|
# The mock server serves test.txt from the e2e directory.
|
|
${payload}= Create Dictionary
|
|
... name=Local Mock Source
|
|
... url=http://127.0.0.1:8765/test_blocklist.txt
|
|
... enabled=${TRUE}
|
|
POST On Session ${sess} /api/v1/blocklists json=${payload} headers=${headers} expected_status=201
|
|
Log Created local mock blocklist source.
|
|
ELSE
|
|
Log Blocklist source already exists — using first available.
|
|
END
|
|
|
|
Start Local Mock Server
|
|
[Documentation] Start a minimal Python HTTP server on port 8765 to serve a test blocklist file.
|
|
... The test.txt file contains one IP per line in plain-text format (fail2ban plain list).
|
|
${mock_file}= Set Variable ${CURDIR}/../../test_blocklist.txt
|
|
${file_exists}= OperatingSystem.File Should Exist ${mock_file}
|
|
Start Process python -m http.server 8765 --bind 127.0.0.1 --directory ${CURDIR}/../../ alias=mockserver
|
|
... stdout=PIPE stderr=STDOUT
|
|
Sleep 2s
|
|
Log Local mock HTTP server started on 127.0.0.1:8765.
|
|
|
|
Cleanup Mock Server
|
|
[Documentation] Stop the mock HTTP server started by Start Local Mock Server.
|
|
${status}= Run Keyword And Return Status Terminate Process mockserver
|
|
Log Mock server cleanup: ${status} |