Files
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

443 lines
16 KiB
Python

"""
Tests for Collection CRUD API endpoints.
Tests the following endpoints:
- GET /library/api/collections
- POST /library/api/collections
- PUT /library/api/collections/<id>
- DELETE /library/api/collections/<id>
"""
import uuid
# Import helpers from conftest
from .conftest import delete_test_collection
class TestGetCollections:
"""Tests for GET /library/api/collections endpoint."""
def test_get_collections_unauthenticated(self, client):
"""Unauthenticated request is rejected."""
response = client.get("/library/api/collections")
# Should redirect to login or return 401
assert response.status_code in [302, 401]
def test_get_collections_authenticated_empty(self, authenticated_client):
"""Returns collections list (may include default Library)."""
response = authenticated_client.get("/library/api/collections")
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert "collections" in data
assert isinstance(data["collections"], list)
def test_get_collections_with_data(
self, authenticated_client, create_collection_helper
):
"""Returns created collections."""
# Create a test collection
created = create_collection_helper(
name="API Test Collection",
description="Testing GET endpoint",
)
assert created is not None
response = authenticated_client.get("/library/api/collections")
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
# Find our created collection
collection_names = [c["name"] for c in data["collections"]]
assert "API Test Collection" in collection_names
def test_get_collections_includes_metadata(
self, authenticated_client, create_collection_helper
):
"""Collections include required metadata fields."""
created = create_collection_helper()
assert created is not None
response = authenticated_client.get("/library/api/collections")
assert response.status_code == 200
data = response.get_json()
assert len(data["collections"]) > 0
# Check metadata fields
coll = data["collections"][0]
assert "id" in coll
assert "name" in coll
assert "description" in coll
assert "created_at" in coll
assert "collection_type" in coll
assert "document_count" in coll
class TestCreateCollection:
"""Tests for POST /library/api/collections endpoint."""
def test_create_collection_unauthenticated(self, client):
"""Unauthenticated request is rejected."""
response = client.post(
"/library/api/collections",
json={"name": "Test", "description": "Test"},
content_type="application/json",
)
assert response.status_code in [302, 401]
def test_create_collection_success(self, authenticated_client):
"""Successfully creates a new collection."""
unique_name = f"Test Collection {uuid.uuid4().hex[:8]}"
response = authenticated_client.post(
"/library/api/collections",
json={
"name": unique_name,
"description": "A test collection",
"type": "user_uploads",
},
content_type="application/json",
)
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert data["collection"]["name"] == unique_name
assert data["collection"]["description"] == "A test collection"
assert "id" in data["collection"]
# Cleanup
delete_test_collection(authenticated_client, data["collection"]["id"])
def test_create_collection_defaults_private(self, authenticated_client):
"""A new collection with no is_public flag defaults to private."""
name = f"Priv {uuid.uuid4().hex[:8]}"
resp = authenticated_client.post(
"/library/api/collections",
json={"name": name},
content_type="application/json",
)
assert resp.status_code == 200
coll = resp.get_json()["collection"]
assert coll["is_public"] is False
delete_test_collection(authenticated_client, coll["id"])
def test_create_and_toggle_collection_is_public(self, authenticated_client):
"""is_public can be set at create and flipped via PUT, and is
reflected in the list API."""
name = f"Pub {uuid.uuid4().hex[:8]}"
resp = authenticated_client.post(
"/library/api/collections",
json={"name": name, "is_public": True},
content_type="application/json",
)
assert resp.status_code == 200
coll = resp.get_json()["collection"]
cid = coll["id"]
assert coll["is_public"] is True
# List reflects it
listing = authenticated_client.get(
"/library/api/collections"
).get_json()["collections"]
match = next(c for c in listing if c["id"] == cid)
assert match["is_public"] is True
# The collection-details load path (documents endpoint) also exposes
# is_public so the details-page toggle can reflect current state.
docs = authenticated_client.get(
f"/library/api/collections/{cid}/documents"
)
assert docs.status_code == 200
assert docs.get_json()["collection"]["is_public"] is True
# Flip back to private via PUT
put = authenticated_client.put(
f"/library/api/collections/{cid}",
json={"is_public": False},
content_type="application/json",
)
assert put.status_code == 200
assert put.get_json()["collection"]["is_public"] is False
delete_test_collection(authenticated_client, cid)
def test_create_collection_empty_name(self, authenticated_client):
"""Rejects collection with empty name."""
response = authenticated_client.post(
"/library/api/collections",
json={"name": "", "description": "No name"},
content_type="application/json",
)
assert response.status_code == 400
data = response.get_json()
assert data["success"] is False
assert (
"required" in data["error"].lower()
or "name" in data["error"].lower()
)
def test_create_collection_whitespace_name(self, authenticated_client):
"""Rejects collection with whitespace-only name."""
response = authenticated_client.post(
"/library/api/collections",
json={"name": " ", "description": "Whitespace name"},
content_type="application/json",
)
assert response.status_code == 400
def test_create_collection_duplicate_name(self, authenticated_client):
"""Rejects collection with duplicate name."""
unique_name = f"Duplicate Test {uuid.uuid4().hex[:8]}"
# Create first collection
response1 = authenticated_client.post(
"/library/api/collections",
json={"name": unique_name, "description": "First"},
content_type="application/json",
)
assert response1.status_code == 200
collection_id = response1.get_json()["collection"]["id"]
try:
# Try to create duplicate
response2 = authenticated_client.post(
"/library/api/collections",
json={"name": unique_name, "description": "Duplicate"},
content_type="application/json",
)
assert response2.status_code == 400
data = response2.get_json()
assert data["success"] is False
assert "exists" in data["error"].lower()
finally:
# Cleanup
delete_test_collection(authenticated_client, collection_id)
def test_create_collection_without_description(self, authenticated_client):
"""Creates collection without description."""
unique_name = f"No Desc Collection {uuid.uuid4().hex[:8]}"
response = authenticated_client.post(
"/library/api/collections",
json={"name": unique_name},
content_type="application/json",
)
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert data["collection"]["name"] == unique_name
# Cleanup
delete_test_collection(authenticated_client, data["collection"]["id"])
class TestUpdateCollection:
"""Tests for PUT /library/api/collections/<id> endpoint."""
def test_update_collection_unauthenticated(self, client):
"""Unauthenticated request is rejected."""
response = client.put(
"/library/api/collections/some-id",
json={"name": "Updated"},
content_type="application/json",
)
assert response.status_code in [302, 401]
def test_update_collection_name(
self, authenticated_client, create_collection_helper
):
"""Successfully updates collection name."""
created = create_collection_helper(name="Original Name")
assert created is not None
new_name = f"Updated Name {uuid.uuid4().hex[:8]}"
response = authenticated_client.put(
f"/library/api/collections/{created['id']}",
json={"name": new_name},
content_type="application/json",
)
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert data["collection"]["name"] == new_name
def test_update_collection_description(
self, authenticated_client, create_collection_helper
):
"""Successfully updates collection description."""
created = create_collection_helper(description="Original description")
assert created is not None
response = authenticated_client.put(
f"/library/api/collections/{created['id']}",
json={"description": "Updated description"},
content_type="application/json",
)
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
assert data["collection"]["description"] == "Updated description"
def test_update_collection_not_found(self, authenticated_client):
"""Returns 404 for non-existent collection."""
fake_id = str(uuid.uuid4())
response = authenticated_client.put(
f"/library/api/collections/{fake_id}",
json={"name": "Updated"},
content_type="application/json",
)
assert response.status_code == 404
data = response.get_json()
assert data["success"] is False
assert "not found" in data["error"].lower()
def test_update_collection_duplicate_name(
self, authenticated_client, create_collection_helper
):
"""Rejects update with duplicate name."""
# Create two collections
coll1 = create_collection_helper(
name=f"Collection A {uuid.uuid4().hex[:8]}"
)
coll2 = create_collection_helper(
name=f"Collection B {uuid.uuid4().hex[:8]}"
)
assert coll1 is not None and coll2 is not None
# Try to rename coll2 to coll1's name
response = authenticated_client.put(
f"/library/api/collections/{coll2['id']}",
json={"name": coll1["name"]},
content_type="application/json",
)
assert response.status_code == 400
data = response.get_json()
assert data["success"] is False
assert "exists" in data["error"].lower()
class TestDeleteCollection:
"""Tests for DELETE /library/api/collections/<id> endpoint."""
def test_delete_collection_unauthenticated(self, client):
"""Unauthenticated request is rejected."""
response = client.delete("/library/api/collections/some-id")
assert response.status_code in [302, 401]
def test_delete_collection_success(self, authenticated_client):
"""Successfully deletes a collection."""
# Create a collection to delete
unique_name = f"To Delete {uuid.uuid4().hex[:8]}"
create_response = authenticated_client.post(
"/library/api/collections",
json={"name": unique_name, "description": "Will be deleted"},
content_type="application/json",
)
assert create_response.status_code == 200
collection_id = create_response.get_json()["collection"]["id"]
# Delete it
response = authenticated_client.delete(
f"/library/api/collections/{collection_id}"
)
assert response.status_code == 200
data = response.get_json()
assert data["success"] is True
# Verify it's gone
list_response = authenticated_client.get("/library/api/collections")
collections = list_response.get_json()["collections"]
collection_ids = [c["id"] for c in collections]
assert collection_id not in collection_ids
def test_delete_collection_not_found(self, authenticated_client):
"""Returns 404 for non-existent collection."""
fake_id = str(uuid.uuid4())
response = authenticated_client.delete(
f"/library/api/collections/{fake_id}"
)
assert response.status_code == 404
data = response.get_json()
assert data["success"] is False
assert "not found" in data["error"].lower()
class TestCollectionIntegration:
"""Integration tests for collection workflow."""
def test_create_read_update_delete_flow(self, authenticated_client):
"""Full CRUD workflow."""
unique_name = f"CRUD Test {uuid.uuid4().hex[:8]}"
# Create
create_response = authenticated_client.post(
"/library/api/collections",
json={"name": unique_name, "description": "Initial"},
content_type="application/json",
)
assert create_response.status_code == 200
collection_id = create_response.get_json()["collection"]["id"]
try:
# Read
list_response = authenticated_client.get("/library/api/collections")
assert list_response.status_code == 200
collections = list_response.get_json()["collections"]
our_coll = next(
(c for c in collections if c["id"] == collection_id), None
)
assert our_coll is not None
assert our_coll["name"] == unique_name
# Update
updated_name = f"Updated {uuid.uuid4().hex[:8]}"
update_response = authenticated_client.put(
f"/library/api/collections/{collection_id}",
json={"name": updated_name, "description": "Updated desc"},
content_type="application/json",
)
assert update_response.status_code == 200
# Verify update
list_response2 = authenticated_client.get(
"/library/api/collections"
)
collections2 = list_response2.get_json()["collections"]
our_coll2 = next(
(c for c in collections2 if c["id"] == collection_id), None
)
assert our_coll2["name"] == updated_name
assert our_coll2["description"] == "Updated desc"
finally:
# Delete
delete_response = authenticated_client.delete(
f"/library/api/collections/{collection_id}"
)
assert delete_response.status_code == 200