2026-03-22 11:52:33 +00:00
|
|
|
"""Integration tests for Wiki API routes"""
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from app import db
|
|
|
|
|
from app.models import Epic, Wiki, wiki_entity_links
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.integration
|
|
|
|
|
class TestWikiRoutes:
|
|
|
|
|
"""Test Wiki API routes"""
|
|
|
|
|
|
|
|
|
|
# ========== GET /boards/<board_id>/wikis ==========
|
|
|
|
|
|
|
|
|
|
def test_get_board_wikis_success(
|
|
|
|
|
self, client, db_session, regular_user, auth_headers, test_board
|
|
|
|
|
):
|
|
|
|
|
"""Test getting all wikis for a board"""
|
|
|
|
|
# Create wikis for board
|
|
|
|
|
wiki1 = Wiki(
|
|
|
|
|
name="Getting Started",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
slug="getting-started",
|
|
|
|
|
content=[
|
|
|
|
|
{"type": "paragraph", "children": [{"text": "Getting started content"}]}
|
|
|
|
|
],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
wiki2 = Wiki(
|
|
|
|
|
name="API Documentation",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
slug="api-documentation",
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "API docs content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki1)
|
|
|
|
|
db_session.add(wiki2)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis", headers=auth_headers
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert len(data) == 2
|
|
|
|
|
assert data[0]["name"] == "Getting Started"
|
|
|
|
|
assert data[1]["name"] == "API Documentation"
|
|
|
|
|
|
|
|
|
|
def test_get_board_wikis_empty(self, client, db_session, auth_headers, test_board):
|
|
|
|
|
"""Test getting wikis when none exist for board"""
|
|
|
|
|
response = client.get(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis", headers=auth_headers
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert len(data) == 0
|
|
|
|
|
|
|
|
|
|
def test_get_board_wikis_board_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test getting wikis for non-existent board"""
|
|
|
|
|
response = client.get("/api/boards/99999/wikis", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert "not found" in data["error"].lower()
|
|
|
|
|
|
|
|
|
|
def test_get_board_wikis_unauthorized(self, client, db_session, test_board):
|
|
|
|
|
"""Test getting wikis without authentication"""
|
|
|
|
|
response = client.get(f"/api/boards/{test_board.id}/wikis")
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
# ========== POST /boards/<board_id>/wikis ==========
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_success(
|
|
|
|
|
self, client, db_session, regular_user, auth_headers, test_board
|
|
|
|
|
):
|
|
|
|
|
"""Test creating a new wiki successfully"""
|
|
|
|
|
wiki_data = {
|
|
|
|
|
"name": "User Guide",
|
|
|
|
|
"slug": "user-guide",
|
|
|
|
|
"content": [{"type": "paragraph", "children": [{"text": "Welcome..."}]}],
|
|
|
|
|
"summary": "A comprehensive user guide",
|
|
|
|
|
"category": "Documentation",
|
|
|
|
|
"tags": ["getting-started", "tutorial"],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=wiki_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["name"] == "User Guide"
|
|
|
|
|
assert data["slug"] == "user-guide"
|
|
|
|
|
assert data["summary"] == "A comprehensive user guide"
|
|
|
|
|
assert data["category"] == "Documentation"
|
|
|
|
|
assert data["tags"] == ["getting-started", "tutorial"]
|
|
|
|
|
assert data["created_by"] == regular_user.id
|
|
|
|
|
assert data["updated_by"] == regular_user.id
|
|
|
|
|
assert "id" in data
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_minimal_data(
|
|
|
|
|
self, client, db_session, auth_headers, test_board
|
|
|
|
|
):
|
|
|
|
|
"""Test creating wiki with only required fields"""
|
|
|
|
|
wiki_data = {
|
|
|
|
|
"name": "Simple Wiki",
|
|
|
|
|
"content": [{"type": "paragraph", "children": [{"text": "Content"}]}],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=wiki_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["name"] == "Simple Wiki"
|
|
|
|
|
assert data["slug"] == "simple-wiki" # Auto-generated
|
|
|
|
|
assert data["summary"] is None
|
|
|
|
|
assert data["category"] is None
|
|
|
|
|
assert data["tags"] is None
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_auto_generate_slug(
|
|
|
|
|
self, client, db_session, auth_headers, test_board
|
|
|
|
|
):
|
|
|
|
|
"""Test creating wiki with auto-generated slug"""
|
|
|
|
|
wiki_data = {
|
|
|
|
|
"name": "API Reference Guide",
|
|
|
|
|
"content": [{"type": "paragraph", "children": [{"text": "Content"}]}],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=wiki_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["slug"] == "api-reference-guide"
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_slug_collision(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test creating wiki with colliding slug"""
|
|
|
|
|
# Create first wiki
|
|
|
|
|
wiki1 = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
slug="test-wiki",
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki1)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
# Create second wiki with same name (should auto-increment slug)
|
|
|
|
|
wiki_data = {
|
|
|
|
|
"name": "Test Wiki",
|
|
|
|
|
"content": [{"type": "paragraph", "children": [{"text": "Content"}]}],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=wiki_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["slug"] == "test-wiki-1"
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_board_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test creating wiki for non-existent board"""
|
|
|
|
|
wiki_data = {
|
|
|
|
|
"name": "Test Wiki",
|
|
|
|
|
"content": [{"type": "paragraph", "children": [{"text": "Content"}]}],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
"/api/boards/99999/wikis",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=wiki_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert "not found" in data["error"].lower()
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_unauthorized(self, client, db_session, test_board):
|
|
|
|
|
"""Test creating wiki without authentication"""
|
|
|
|
|
wiki_data = {
|
|
|
|
|
"name": "Test Wiki",
|
|
|
|
|
"content": [{"type": "paragraph", "children": [{"text": "Content"}]}],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis",
|
|
|
|
|
json=wiki_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_missing_name(
|
|
|
|
|
self, client, db_session, auth_headers, test_board
|
|
|
|
|
):
|
|
|
|
|
"""Test creating wiki without name"""
|
|
|
|
|
wiki_data = {"summary": "No name provided"}
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=wiki_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_name_too_long(
|
|
|
|
|
self, client, db_session, auth_headers, test_board
|
|
|
|
|
):
|
|
|
|
|
"""Test creating wiki with name exceeding max length"""
|
|
|
|
|
wiki_data = {
|
|
|
|
|
"name": "A" * 201,
|
|
|
|
|
"content": [{"type": "paragraph", "children": [{"text": "Content"}]}],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=wiki_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_invalid_slug(
|
|
|
|
|
self, client, db_session, auth_headers, test_board
|
|
|
|
|
):
|
|
|
|
|
"""Test creating wiki with invalid slug format"""
|
|
|
|
|
wiki_data = {
|
|
|
|
|
"name": "Test Wiki",
|
|
|
|
|
"slug": "invalid slug!",
|
|
|
|
|
"content": [{"type": "paragraph", "children": [{"text": "Content"}]}],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/boards/{test_board.id}/wikis",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=wiki_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
|
|
|
|
# ========== GET /wikis/<wiki_id> ==========
|
|
|
|
|
|
|
|
|
|
def test_get_wiki_success(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test getting a specific wiki with details"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
slug="test-wiki",
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(f"/api/wikis/{wiki.id}", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["id"] == wiki.id
|
|
|
|
|
assert data["name"] == "Test Wiki"
|
|
|
|
|
assert data["slug"] == "test-wiki"
|
|
|
|
|
assert "linked_cards" in data
|
|
|
|
|
assert "linked_epics" in data
|
|
|
|
|
|
|
|
|
|
def test_get_wiki_with_linked_entities(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user, test_card
|
|
|
|
|
):
|
|
|
|
|
"""Test getting wiki with linked cards and epics"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
slug="test-wiki",
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
# Link card to wiki
|
|
|
|
|
db.session.execute(
|
|
|
|
|
wiki_entity_links.insert().values(
|
|
|
|
|
wiki_id=wiki.id, entity_type="card", entity_id=test_card.id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(f"/api/wikis/{wiki.id}", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert "linked_cards" in data
|
|
|
|
|
assert "linked_epics" in data
|
|
|
|
|
assert len(data["linked_cards"]) == 1
|
|
|
|
|
assert data["linked_cards"][0]["id"] == test_card.id
|
|
|
|
|
|
|
|
|
|
def test_get_wiki_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test getting non-existent wiki"""
|
|
|
|
|
response = client.get("/api/wikis/99999", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert "not found" in data["error"].lower()
|
|
|
|
|
|
|
|
|
|
def test_get_wiki_unauthorized(self, client, db_session, test_board, regular_user):
|
|
|
|
|
"""Test getting wiki without authentication"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
slug="test-wiki",
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.get(f"/api/wikis/{wiki.id}")
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
# ========== PUT /wikis/<wiki_id> ==========
|
|
|
|
|
|
|
|
|
|
def test_update_wiki_success(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test updating a wiki successfully"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Original Name",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
slug="original-slug",
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Original content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
update_data = {
|
|
|
|
|
"name": "Updated Name",
|
|
|
|
|
"slug": "updated-slug",
|
|
|
|
|
"summary": "Updated summary",
|
|
|
|
|
"category": "Documentation",
|
|
|
|
|
"tags": ["updated", "tag"],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.put(
|
|
|
|
|
f"/api/wikis/{wiki.id}",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=update_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["name"] == "Updated Name"
|
|
|
|
|
assert data["slug"] == "updated-slug"
|
|
|
|
|
assert data["summary"] == "Updated summary"
|
|
|
|
|
assert data["category"] == "Documentation"
|
|
|
|
|
assert data["tags"] == ["updated", "tag"]
|
|
|
|
|
|
|
|
|
|
def test_update_wiki_partial(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test updating wiki with partial data"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Original Name",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Original content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
update_data = {"name": "Updated Name"}
|
|
|
|
|
|
|
|
|
|
response = client.put(
|
|
|
|
|
f"/api/wikis/{wiki.id}",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json=update_data,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["name"] == "Updated Name"
|
|
|
|
|
# Other fields should remain unchanged
|
|
|
|
|
|
|
|
|
|
def test_update_wiki_content(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test updating wiki content"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Original content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
new_content = [{"type": "heading", "children": [{"text": "Updated Content"}]}]
|
|
|
|
|
|
|
|
|
|
response = client.put(
|
|
|
|
|
f"/api/wikis/{wiki.id}",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"content": new_content},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert data["content"] == new_content
|
|
|
|
|
|
|
|
|
|
def test_update_wiki_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test updating non-existent wiki"""
|
|
|
|
|
response = client.put(
|
|
|
|
|
"/api/wikis/99999",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"name": "Updated"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
def test_update_wiki_unauthorized(
|
|
|
|
|
self, client, db_session, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test updating wiki without authentication"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.put(f"/api/wikis/{wiki.id}", json={"name": "Updated"})
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
def test_update_wiki_invalid_name(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test updating wiki with invalid name"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.put(
|
|
|
|
|
f"/api/wikis/{wiki.id}",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"name": ""},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
|
|
|
|
def test_update_wiki_invalid_slug(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test updating wiki with invalid slug"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.put(
|
|
|
|
|
f"/api/wikis/{wiki.id}",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"slug": "invalid slug!"},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
|
|
|
|
# ========== DELETE /wikis/<wiki_id> ==========
|
|
|
|
|
|
|
|
|
|
def test_delete_wiki_success(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test deleting a wiki successfully"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="To Delete",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
wiki_id = wiki.id
|
|
|
|
|
|
|
|
|
|
response = client.delete(f"/api/wikis/{wiki.id}", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert "message" in data
|
|
|
|
|
|
|
|
|
|
# Verify wiki is deleted
|
|
|
|
|
deleted_wiki = db.session.get(Wiki, wiki_id)
|
2026-05-01 15:03:00 +00:00
|
|
|
assert deleted_wiki is not None
|
|
|
|
|
assert deleted_wiki.status == "deleted"
|
2026-03-22 11:52:33 +00:00
|
|
|
|
|
|
|
|
def test_delete_wiki_with_links(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user, test_card
|
|
|
|
|
):
|
2026-05-01 15:03:00 +00:00
|
|
|
"""Test deleting wiki (soft delete) preserves entity links"""
|
2026-03-22 11:52:33 +00:00
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Wiki with Links",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
# Link card to wiki
|
|
|
|
|
db.session.execute(
|
|
|
|
|
wiki_entity_links.insert().values(
|
|
|
|
|
wiki_id=wiki.id, entity_type="card", entity_id=test_card.id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.delete(f"/api/wikis/{wiki.id}", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
2026-05-01 15:03:00 +00:00
|
|
|
# Verify wiki is soft-deleted
|
|
|
|
|
deleted_wiki = db.session.get(Wiki, wiki.id)
|
|
|
|
|
assert deleted_wiki is not None
|
|
|
|
|
assert deleted_wiki.status == "deleted"
|
|
|
|
|
|
|
|
|
|
# Verify links are preserved (not deleted with soft delete)
|
2026-03-22 11:52:33 +00:00
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
|
|
|
|
links = db.session.scalars(
|
|
|
|
|
select(wiki_entity_links).where(wiki_entity_links.c.wiki_id == wiki.id)
|
|
|
|
|
).all()
|
2026-05-01 15:03:00 +00:00
|
|
|
assert len(links) == 1 # Links are preserved
|
2026-03-22 11:52:33 +00:00
|
|
|
|
|
|
|
|
def test_delete_wiki_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test deleting non-existent wiki"""
|
|
|
|
|
response = client.delete("/api/wikis/99999", headers=auth_headers)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
def test_delete_wiki_unauthorized(
|
|
|
|
|
self, client, db_session, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test deleting wiki without authentication"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.delete(f"/api/wikis/{wiki.id}")
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
# ========== POST /wikis/<wiki_id>/links ==========
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_link_card(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user, test_card
|
|
|
|
|
):
|
|
|
|
|
"""Test linking wiki to card successfully"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "card", "entity_id": test_card.id},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert "message" in data
|
|
|
|
|
|
|
|
|
|
# Verify link is created
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
|
|
|
|
link = db.session.scalars(
|
|
|
|
|
select(wiki_entity_links).where(
|
|
|
|
|
wiki_entity_links.c.wiki_id == wiki.id,
|
|
|
|
|
wiki_entity_links.c.entity_type == "card",
|
|
|
|
|
wiki_entity_links.c.entity_id == test_card.id,
|
|
|
|
|
)
|
|
|
|
|
).first()
|
|
|
|
|
assert link is not None
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_link_epic(
|
|
|
|
|
self,
|
|
|
|
|
client,
|
|
|
|
|
db_session,
|
|
|
|
|
auth_headers,
|
|
|
|
|
test_board,
|
|
|
|
|
regular_user,
|
|
|
|
|
test_list,
|
|
|
|
|
):
|
|
|
|
|
"""Test linking wiki to epic successfully"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
epic = Epic(name="Test Epic", board_id=test_board.id)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.add(epic)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "epic", "entity_id": epic.id},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
|
|
|
|
|
# Verify link is created
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
|
|
|
|
link = db.session.scalars(
|
|
|
|
|
select(wiki_entity_links).where(
|
|
|
|
|
wiki_entity_links.c.wiki_id == wiki.id,
|
|
|
|
|
wiki_entity_links.c.entity_type == "epic",
|
|
|
|
|
wiki_entity_links.c.entity_id == epic.id,
|
|
|
|
|
)
|
|
|
|
|
).first()
|
|
|
|
|
assert link is not None
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_link_list(
|
|
|
|
|
self,
|
|
|
|
|
client,
|
|
|
|
|
db_session,
|
|
|
|
|
auth_headers,
|
|
|
|
|
test_board,
|
|
|
|
|
regular_user,
|
|
|
|
|
test_list,
|
|
|
|
|
):
|
|
|
|
|
"""Test linking wiki to list successfully"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "list", "entity_id": test_list.id},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_link_board(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test linking wiki to board successfully"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "board", "entity_id": test_board.id},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_link_already_exists(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user, test_card
|
|
|
|
|
):
|
|
|
|
|
"""Test creating duplicate link"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
# Create first link
|
|
|
|
|
db.session.execute(
|
|
|
|
|
wiki_entity_links.insert().values(
|
|
|
|
|
wiki_id=wiki.id, entity_type="card", entity_id=test_card.id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
# Try to create duplicate link
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "card", "entity_id": test_card.id},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert "already exists" in data["error"].lower()
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_link_wiki_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test linking non-existent wiki"""
|
|
|
|
|
response = client.post(
|
|
|
|
|
"/api/wikis/99999/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "card", "entity_id": 1},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_link_entity_not_found(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test linking to non-existent entity"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "card", "entity_id": 99999},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
def test_create_wiki_link_unauthorized(
|
|
|
|
|
self, client, db_session, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test creating link without authentication"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.post(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
json={"entity_type": "card", "entity_id": 1},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
|
# ========== DELETE /wikis/<wiki_id>/links ==========
|
|
|
|
|
|
|
|
|
|
def test_delete_wiki_link_success(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user, test_card
|
|
|
|
|
):
|
|
|
|
|
"""Test deleting a wiki link successfully"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
# Create link
|
|
|
|
|
db.session.execute(
|
|
|
|
|
wiki_entity_links.insert().values(
|
|
|
|
|
wiki_id=wiki.id, entity_type="card", entity_id=test_card.id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.delete(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "card", "entity_id": test_card.id},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
data = response.get_json()
|
|
|
|
|
assert "message" in data
|
|
|
|
|
|
|
|
|
|
# Verify link is deleted
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
|
|
|
|
link = db.session.scalars(
|
|
|
|
|
select(wiki_entity_links).where(
|
|
|
|
|
wiki_entity_links.c.wiki_id == wiki.id,
|
|
|
|
|
wiki_entity_links.c.entity_type == "card",
|
|
|
|
|
wiki_entity_links.c.entity_id == test_card.id,
|
|
|
|
|
)
|
|
|
|
|
).first()
|
|
|
|
|
assert link is None
|
|
|
|
|
|
|
|
|
|
def test_delete_wiki_link_wiki_not_found(self, client, db_session, auth_headers):
|
|
|
|
|
"""Test deleting link for non-existent wiki"""
|
|
|
|
|
response = client.delete(
|
|
|
|
|
"/api/wikis/99999/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "card", "entity_id": 1},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
def test_delete_wiki_link_not_found(
|
|
|
|
|
self, client, db_session, auth_headers, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test deleting non-existent link"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.delete(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
headers=auth_headers,
|
|
|
|
|
json={"entity_type": "card", "entity_id": 99999},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
|
|
|
|
def test_delete_wiki_link_unauthorized(
|
|
|
|
|
self, client, db_session, test_board, regular_user
|
|
|
|
|
):
|
|
|
|
|
"""Test deleting link without authentication"""
|
|
|
|
|
wiki = Wiki(
|
|
|
|
|
name="Test Wiki",
|
|
|
|
|
board_id=test_board.id,
|
|
|
|
|
content=[{"type": "paragraph", "children": [{"text": "Test content"}]}],
|
|
|
|
|
created_by=regular_user.id,
|
|
|
|
|
)
|
|
|
|
|
db_session.add(wiki)
|
|
|
|
|
db_session.commit()
|
|
|
|
|
|
|
|
|
|
response = client.delete(
|
|
|
|
|
f"/api/wikis/{wiki.id}/links",
|
|
|
|
|
json={"entity_type": "card", "entity_id": 1},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == 401
|