From d91a3bc088fb39849fff554b7ae88a92b16f2e17 Mon Sep 17 00:00:00 2001 From: Tessa Walsh Date: Tue, 15 Jul 2025 21:05:57 -0400 Subject: [PATCH] Run webhook tests nightly (#2738) Fixes #2737 - Moves webhook-related tests to run nightly, to speed up CI runs and avoid the periodic failures we've been getting lately. - Also ensures all try/except blocks that have time.sleep in the 'try' also have a time.sleep in 'except' to avoid fast-looping retries --------- Co-authored-by: Ilya Kreymer --- .github/workflows/k3d-nightly-ci.yaml | 3 + backend/test/conftest.py | 3 +- backend/test/test_org.py | 81 ---------- backend/test/test_uploads.py | 2 +- backend/test/test_z_delete_org.py | 5 +- backend/test_nightly/data/example.wacz | Bin 0 -> 16337 bytes backend/test_nightly/echo_server.py | 39 +++++ backend/test_nightly/test_org_deletion.py | 8 +- .../{test => test_nightly}/test_webhooks.py | 153 +++++++++++++++++- 9 files changed, 202 insertions(+), 92 deletions(-) create mode 100644 backend/test_nightly/data/example.wacz create mode 100644 backend/test_nightly/echo_server.py rename backend/{test => test_nightly}/test_webhooks.py (69%) diff --git a/.github/workflows/k3d-nightly-ci.yaml b/.github/workflows/k3d-nightly-ci.yaml index d27925af..9d08ab3c 100644 --- a/.github/workflows/k3d-nightly-ci.yaml +++ b/.github/workflows/k3d-nightly-ci.yaml @@ -7,6 +7,9 @@ on: workflow_dispatch: +env: + ECHO_SERVER_HOST_URL: http://host.k3d.internal:18080 + jobs: collect-test-modules: runs-on: ubuntu-latest diff --git a/backend/test/conftest.py b/backend/test/conftest.py index abc7234b..cc4f30df 100644 --- a/backend/test/conftest.py +++ b/backend/test/conftest.py @@ -1,7 +1,6 @@ import os import pytest import requests -import socket import subprocess import time from typing import Dict @@ -691,7 +690,7 @@ def prepare_browser_for_profile_commit( break time.sleep(5) except: - pass + time.sleep(5) attempts += 1 diff --git a/backend/test/test_org.py b/backend/test/test_org.py index 5c41a0fa..8d768751 100644 --- a/backend/test/test_org.py +++ b/backend/test/test_org.py @@ -485,87 +485,6 @@ def test_delete_invite_by_email(admin_auth_headers, non_default_org_id): assert data["detail"] == "invite_not_found" -def test_update_event_webhook_urls_org_admin(admin_auth_headers, default_org_id): - # Verify no URLs are configured - r = requests.get( - f"{API_PREFIX}/orgs/{default_org_id}", - headers=admin_auth_headers, - ) - assert r.status_code == 200 - data = r.json() - if data.get("webhooks"): - webhooks = data.get("webhooks") - assert webhooks.get("crawlStarted") is None - assert webhooks.get("crawlFinished") is None - assert webhooks.get("crawlDeleted") is None - assert webhooks.get("uploadFinished") is None - assert webhooks.get("uploadDeleted") is None - assert webhooks.get("addedToCollection") is None - assert webhooks.get("removedFromCollection") is None - assert webhooks.get("collectionDeleted") is None - - # Set URLs and verify - CRAWL_STARTED_URL = "https://example.com/crawl/started" - CRAWL_FINISHED_URL = "https://example.com/crawl/finished" - CRAWL_DELETED_URL = "https://example.com/crawl/deleted" - UPLOAD_FINISHED_URL = "https://example.com/upload/finished" - UPLOAD_DELETED_URL = "https://example.com/upload/deleted" - COLL_ADDED_URL = "https://example.com/coll/added" - COLL_REMOVED_URL = "http://example.com/coll/removed" - COLL_DELETED_URL = "http://example.com/coll/deleted" - - r = requests.post( - f"{API_PREFIX}/orgs/{default_org_id}/event-webhook-urls", - headers=admin_auth_headers, - json={ - "crawlStarted": CRAWL_STARTED_URL, - "crawlFinished": CRAWL_FINISHED_URL, - "crawlDeleted": CRAWL_DELETED_URL, - "uploadFinished": UPLOAD_FINISHED_URL, - "uploadDeleted": UPLOAD_DELETED_URL, - "addedToCollection": COLL_ADDED_URL, - "removedFromCollection": COLL_REMOVED_URL, - "collectionDeleted": COLL_DELETED_URL, - }, - ) - assert r.status_code == 200 - assert r.json()["updated"] - - r = requests.get( - f"{API_PREFIX}/orgs/{default_org_id}", - headers=admin_auth_headers, - ) - assert r.status_code == 200 - data = r.json() - urls = data["webhookUrls"] - assert urls["crawlStarted"] == CRAWL_STARTED_URL - assert urls["crawlFinished"] == CRAWL_FINISHED_URL - assert urls["crawlDeleted"] == CRAWL_DELETED_URL - - assert urls["uploadFinished"] == UPLOAD_FINISHED_URL - assert urls["uploadDeleted"] == UPLOAD_DELETED_URL - - assert urls["addedToCollection"] == COLL_ADDED_URL - assert urls["removedFromCollection"] == COLL_REMOVED_URL - assert urls["collectionDeleted"] == COLL_DELETED_URL - - -def test_update_event_webhook_urls_org_crawler(crawler_auth_headers, default_org_id): - r = requests.post( - f"{API_PREFIX}/orgs/{default_org_id}/event-webhook-urls", - headers=crawler_auth_headers, - json={ - "crawlStarted": "https://example.com/crawlstarted", - "crawlFinished": "https://example.com/crawlfinished", - "uploadFinished": "https://example.com/uploadfinished", - "addedToCollection": "https://example.com/added", - "removedFromCollection": "https://example.com/removed", - }, - ) - assert r.status_code == 403 - assert r.json()["detail"] == "User does not have permission to perform this action" - - def test_org_metrics(crawler_auth_headers, default_org_id): r = requests.get( f"{API_PREFIX}/orgs/{default_org_id}/metrics", diff --git a/backend/test/test_uploads.py b/backend/test/test_uploads.py index 18a78800..1dd88b52 100644 --- a/backend/test/test_uploads.py +++ b/backend/test/test_uploads.py @@ -308,7 +308,7 @@ def test_uploads_collection_updated( assert data["totalSize"] > 0 assert data["dateEarliest"] assert data["dateLatest"] - assert data["modified"] > data["created"] + assert data["modified"] >= data["created"] def test_replace_upload( diff --git a/backend/test/test_z_delete_org.py b/backend/test/test_z_delete_org.py index fbb9b0f0..ecf4910a 100644 --- a/backend/test/test_z_delete_org.py +++ b/backend/test/test_z_delete_org.py @@ -1,4 +1,5 @@ import requests +import time from .conftest import API_PREFIX @@ -39,7 +40,7 @@ def test_recalculate_org_storage(admin_auth_headers, default_org_id): time.sleep(10) except: - pass + time.sleep(10) attempts += 1 @@ -112,7 +113,7 @@ def test_delete_org_superadmin(admin_auth_headers, default_org_id): time.sleep(10) except: - pass + time.sleep(10) attempts += 1 diff --git a/backend/test_nightly/data/example.wacz b/backend/test_nightly/data/example.wacz new file mode 100644 index 0000000000000000000000000000000000000000..840227ef70168bc8e82e3495dc4ce3640dbb593a GIT binary patch literal 16337 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 Rp*aM6RDgU#ud{nc{{t$?>d62A literal 0 HcmV?d00001 diff --git a/backend/test_nightly/echo_server.py b/backend/test_nightly/echo_server.py new file mode 100644 index 00000000..0da8715a --- /dev/null +++ b/backend/test_nightly/echo_server.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +""" +A web server to record POST requests and return them on a GET request +""" +from http.server import HTTPServer, BaseHTTPRequestHandler +import json + +BIND_HOST = "0.0.0.0" +PORT = 18080 + +post_bodies = [] + + +class EchoServerHTTPRequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.end_headers() + self.wfile.write(json.dumps({"post_bodies": post_bodies}).encode("utf-8")) + + def do_POST(self): + content_length = int(self.headers.get("content-length", 0)) + body = self.rfile.read(content_length) + self.send_response(200) + if self.path.endswith("/portalUrl"): + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write( + json.dumps({"portalUrl": "https://portal.example.com/path/"}).encode( + "utf-8" + ) + ) + else: + self.end_headers() + + post_bodies.append(json.loads(body.decode("utf-8").replace("'", '"'))) + + +httpd = HTTPServer((BIND_HOST, PORT), EchoServerHTTPRequestHandler) +httpd.serve_forever() diff --git a/backend/test_nightly/test_org_deletion.py b/backend/test_nightly/test_org_deletion.py index c8379936..291ba01b 100644 --- a/backend/test_nightly/test_org_deletion.py +++ b/backend/test_nightly/test_org_deletion.py @@ -168,7 +168,9 @@ def test_delete_org_crawl_running( time.sleep(10) except: - pass + time.sleep(10) + + attempts += 1 @@ -214,7 +216,7 @@ def test_delete_org_qa_running( time.sleep(10) except: - pass + time.sleep(10) attempts += 1 @@ -260,7 +262,7 @@ def test_delete_org_profile_running( time.sleep(10) except: - pass + time.sleep(10) attempts += 1 diff --git a/backend/test/test_webhooks.py b/backend/test_nightly/test_webhooks.py similarity index 69% rename from backend/test/test_webhooks.py rename to backend/test_nightly/test_webhooks.py index fad3244f..f617c72d 100644 --- a/backend/test/test_webhooks.py +++ b/backend/test_nightly/test_webhooks.py @@ -1,7 +1,9 @@ import json import os +import subprocess import time +import pytest import requests from .conftest import API_PREFIX @@ -20,8 +22,150 @@ ECHO_SERVER_URL_FROM_K8S = os.environ.get( "ECHO_SERVER_HOST_URL", "http://host.docker.internal:18080" ) +FAILED_STATES = ["canceled", "failed", "skipped_quota_reached"] -def test_list_webhook_events(admin_auth_headers, default_org_id): +SUCCESSFUL_STATES = ["complete", "stopped_by_user", "stopped_quota_reached"] + +FINISHED_STATES = [*FAILED_STATES, *SUCCESSFUL_STATES] + + +@pytest.fixture(scope="function") +def echo_server(): + print(f"Echo server starting", flush=True) + p = subprocess.Popen(["python3", os.path.join(curr_dir, "echo_server.py")]) + print(f"Echo server started", flush=True) + time.sleep(1) + yield p + time.sleep(10) + print(f"Echo server terminating", flush=True) + p.terminate() + print(f"Echo server terminated", flush=True) + + +@pytest.fixture(scope="session") +def all_crawls_crawl_id(crawler_auth_headers, default_org_id): + # Start crawl. + crawl_data = { + "runNow": True, + "name": "All Crawls Test Crawl", + "description": "Lorem ipsum", + "config": { + "seeds": [{"url": "https://webrecorder.net/"}], + "exclude": "community", + "limit": 3, + }, + } + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/crawlconfigs/", + headers=crawler_auth_headers, + json=crawl_data, + ) + data = r.json() + crawl_id = data["run_now_job"] + + # Wait for it to complete and then return crawl ID + while True: + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}/crawls/{crawl_id}/replay.json", + headers=crawler_auth_headers, + ) + data = r.json() + if data["state"] in FINISHED_STATES: + break + time.sleep(5) + + # Add description to crawl + r = requests.patch( + f"{API_PREFIX}/orgs/{default_org_id}/crawls/{crawl_id}", + headers=crawler_auth_headers, + json={"description": "Lorem ipsum"}, + ) + assert r.status_code == 200 + return crawl_id + + +def test_update_event_webhook_urls_org_admin(admin_auth_headers, default_org_id): + # Verify no URLs are configured + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}", + headers=admin_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + if data.get("webhooks"): + webhooks = data.get("webhooks") + assert webhooks.get("crawlStarted") is None + assert webhooks.get("crawlFinished") is None + assert webhooks.get("crawlDeleted") is None + assert webhooks.get("uploadFinished") is None + assert webhooks.get("uploadDeleted") is None + assert webhooks.get("addedToCollection") is None + assert webhooks.get("removedFromCollection") is None + assert webhooks.get("collectionDeleted") is None + + # Set URLs and verify + CRAWL_STARTED_URL = "https://example.com/crawl/started" + CRAWL_FINISHED_URL = "https://example.com/crawl/finished" + CRAWL_DELETED_URL = "https://example.com/crawl/deleted" + UPLOAD_FINISHED_URL = "https://example.com/upload/finished" + UPLOAD_DELETED_URL = "https://example.com/upload/deleted" + COLL_ADDED_URL = "https://example.com/coll/added" + COLL_REMOVED_URL = "http://example.com/coll/removed" + COLL_DELETED_URL = "http://example.com/coll/deleted" + + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/event-webhook-urls", + headers=admin_auth_headers, + json={ + "crawlStarted": CRAWL_STARTED_URL, + "crawlFinished": CRAWL_FINISHED_URL, + "crawlDeleted": CRAWL_DELETED_URL, + "uploadFinished": UPLOAD_FINISHED_URL, + "uploadDeleted": UPLOAD_DELETED_URL, + "addedToCollection": COLL_ADDED_URL, + "removedFromCollection": COLL_REMOVED_URL, + "collectionDeleted": COLL_DELETED_URL, + }, + ) + assert r.status_code == 200 + assert r.json()["updated"] + + r = requests.get( + f"{API_PREFIX}/orgs/{default_org_id}", + headers=admin_auth_headers, + ) + assert r.status_code == 200 + data = r.json() + urls = data["webhookUrls"] + assert urls["crawlStarted"] == CRAWL_STARTED_URL + assert urls["crawlFinished"] == CRAWL_FINISHED_URL + assert urls["crawlDeleted"] == CRAWL_DELETED_URL + + assert urls["uploadFinished"] == UPLOAD_FINISHED_URL + assert urls["uploadDeleted"] == UPLOAD_DELETED_URL + + assert urls["addedToCollection"] == COLL_ADDED_URL + assert urls["removedFromCollection"] == COLL_REMOVED_URL + assert urls["collectionDeleted"] == COLL_DELETED_URL + + +def test_update_event_webhook_urls_org_crawler(crawler_auth_headers, default_org_id): + r = requests.post( + f"{API_PREFIX}/orgs/{default_org_id}/event-webhook-urls", + headers=crawler_auth_headers, + json={ + "crawlStarted": "https://example.com/crawlstarted", + "crawlFinished": "https://example.com/crawlfinished", + "uploadFinished": "https://example.com/uploadfinished", + "addedToCollection": "https://example.com/added", + "removedFromCollection": "https://example.com/removed", + }, + ) + assert r.status_code == 403 + assert r.json()["detail"] == "User does not have permission to perform this action" + + +def test_list_webhook_events(admin_auth_headers, default_org_id, crawl_id_wr): # Verify that webhook URLs have been set in previous tests r = requests.get( f"{API_PREFIX}/orgs/{default_org_id}", @@ -40,6 +184,8 @@ def test_list_webhook_events(admin_auth_headers, default_org_id): assert urls["collectionDeleted"] # Verify list endpoint works as expected + # At this point we expect webhook attempts to fail since they're not + # configured against a valid endpoint r = requests.get( f"{API_PREFIX}/orgs/{default_org_id}/webhooks", headers=admin_auth_headers, @@ -62,7 +208,7 @@ def test_list_webhook_events(admin_auth_headers, default_org_id): assert _webhook_event_id -def test_get_webhook_event(admin_auth_headers, default_org_id): +def test_get_webhook_event(admin_auth_headers, default_org_id, crawl_id_wr): r = requests.get( f"{API_PREFIX}/orgs/{default_org_id}/webhooks/{_webhook_event_id}", headers=admin_auth_headers, @@ -99,7 +245,7 @@ def test_get_webhook_event(admin_auth_headers, default_org_id): assert len(body["itemIds"]) >= 1 -def test_retry_webhook_event(admin_auth_headers, default_org_id): +def test_retry_webhook_event(admin_auth_headers, default_org_id, crawl_id_wr): # Expect to fail because we haven't set up URLs that accept webhooks r = requests.post( f"{API_PREFIX}/orgs/{default_org_id}/webhooks/{_webhook_event_id}/retry", @@ -175,6 +321,7 @@ def test_webhooks_sent( "autoAddCollections": [webhooks_coll_id], "config": { "seeds": [{"url": "https://webrecorder.net/"}], + "limit": 2, }, } r = requests.post(