# retoor import time from datetime import datetime, timezone, timedelta import pytest import requests from tests.conftest import BASE_URL from devplacepy.database import get_table, refresh_snapshot, set_setting from devplacepy.utils import generate_uid JSON = {"Accept": "application/json"} _counter = [0] @pytest.fixture(scope="module", autouse=True) def _devlog_test_settings(app_server): for key, value in { "rate_limit_per_minute": "1000000", "rate_limit_window_seconds": "60", "registration_open": "1", "maintenance_mode": "0", }.items(): set_setting(key, value) yield def _unique(prefix="dl"): _counter[0] += 1 return f"{prefix}{int(time.time() * 1000)}{_counter[0]}" def _member(): name = _unique("dlmem") s = requests.Session() s.post( f"{BASE_URL}/auth/signup", data={ "username": name, "email": f"{name}@t.dev", "password": "secret123", "confirm_password": "secret123", }, allow_redirects=True, ) return s, name def _db_user(name): refresh_snapshot() return get_table("users").find_one(username=name) def _create_project(session, title=None): title = title or _unique("dlproj") r = session.post( f"{BASE_URL}/projects/create", headers=JSON, data={ "title": title, "description": "Devlog test project", "project_type": "software", "status": "In Development", "platforms": "", }, ) assert r.status_code == 200, r.text[:300] return r.json()["data"] def _create_post(session, content, project_uid, title=None): title = title or _unique("dlpost") r = session.post( f"{BASE_URL}/posts/create", headers=JSON, data={ "title": title, "content": content, "topic": "devlog", "project_uid": project_uid, }, ) assert r.status_code == 200, r.text[:300] return r.json()["data"] def _create_post_direct(project_uid, user_uid, order, marker=None): """Insert a post directly into DB with precise created_at ordering.""" uid = generate_uid() marker = marker or f"dlpost-{uid[:8]}" get_table("posts").insert( { "deleted_at": None, "deleted_by": None, "uid": uid, "user_uid": user_uid, "slug": f"{uid[:8]}-devlog-post", "title": marker, "content": f"Devlog post content {order}", "topic": "devlog", "project_uid": project_uid, "image": None, "stars": 0, "created_at": (datetime(2026, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=order)).isoformat(), } ) refresh_snapshot() return uid, marker def test_devlog_empty_state_when_no_posts(app_server): """Project with no linked posts returns empty devlog_posts list.""" session, _ = _member() project = _create_project(session) slug = project["slug"] or project["uid"] r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON) assert r.status_code == 200, r.text[:300] body = r.json() assert body["devlog_posts"] == [] assert body["devlog_next_cursor"] is None def test_devlog_shows_linked_post(app_server): """A post linked via project_uid appears in the project's devlog.""" session, name = _member() project = _create_project(session) slug = project["slug"] or project["uid"] marker = _unique("dllink") _create_post(session, marker, project["uid"], title=marker) r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON) assert r.status_code == 200, r.text[:300] body = r.json() assert len(body["devlog_posts"]) == 1 assert body["devlog_next_cursor"] is None post_item = body["devlog_posts"][0] assert post_item["post"]["title"] == marker assert post_item["author"]["username"] == name def test_devlog_reverse_chronological_order(app_server): """Multiple linked posts appear newest-first.""" session, name = _member() project = _create_project(session) slug = project["slug"] or project["uid"] user = _db_user(name) project_uid = project["uid"] markers = [] for i in range(3): marker = f"dlorder-{i}-{generate_uid()[:8]}" markers.append(marker) _create_post_direct(project_uid, user["uid"], i, marker=marker) r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON) assert r.status_code == 200, r.text[:300] body = r.json() titles = [item["post"]["title"] for item in body["devlog_posts"]] assert titles == list(reversed(markers)), ( f"Expected newest-first order: {list(reversed(markers))}, got: {titles}" ) def test_devlog_pagination(app_server): """More than PAGE_SIZE posts produce next_cursor.""" from devplacepy.database.pagination import PAGE_SIZE session, name = _member() project = _create_project(session) slug = project["slug"] or project["uid"] user = _db_user(name) project_uid = project["uid"] count = PAGE_SIZE + 1 for i in range(count): _create_post_direct(project_uid, user["uid"], i, marker=f"dlpag-{i}") r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON) assert r.status_code == 200, r.text[:300] body = r.json() assert len(body["devlog_posts"]) == PAGE_SIZE, ( f"Expected {PAGE_SIZE} posts on first page, got {len(body['devlog_posts'])}" ) assert body["devlog_next_cursor"] is not None, ( "Expected next_cursor when more than PAGE_SIZE posts exist" ) before = body["devlog_next_cursor"] r2 = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON, params={"before": before}) assert r2.status_code == 200, r2.text[:300] body2 = r2.json() assert len(body2["devlog_posts"]) == 1, ( f"Expected 1 post on second page, got {len(body2['devlog_posts'])}" ) assert body2["devlog_next_cursor"] is None, ( "Expected no next_cursor on last page" ) r_html = session.get(f"{BASE_URL}/projects/{slug}") assert r_html.status_code == 200 assert 'class="load-more-wrap"' in r_html.text, ( "Expected Load More button in HTML for paginated devlog" ) def test_devlog_excludes_unlinked_posts(app_server): """Posts without project_uid do not appear in any project's devlog.""" session, _ = _member() project = _create_project(session) slug = project["slug"] or project["uid"] unlinked = _unique("dlnolink") session.post( f"{BASE_URL}/posts/create", headers=JSON, data={ "title": unlinked, "content": "This post has no project", "topic": "devlog", }, ) r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON) assert r.status_code == 200, r.text[:300] body = r.json() titles = [item["post"]["title"] for item in body["devlog_posts"]] assert unlinked not in titles, ( "Post without project_uid must not appear in devlog" ) def test_devlog_enriches_author_and_metadata(app_server): """Devlog posts include author data, comment count, and vote info.""" session, name = _member() user = _db_user(name) project = _create_project(session) slug = project["slug"] or project["uid"] marker = _unique("dlenrich") post_data = _create_post(session, marker, project["uid"], title=marker) r = session.get(f"{BASE_URL}/projects/{slug}", headers=JSON) assert r.status_code == 200, r.text[:300] body = r.json() assert len(body["devlog_posts"]) == 1 item = body["devlog_posts"][0] assert item["author"]["username"] == name assert item["author"]["uid"] == user["uid"] assert isinstance(item["my_vote"], int) assert isinstance(item["comment_count"], int) assert item["comment_count"] == 0 assert item["post"]["uid"] == post_data["uid"] assert item["post"]["slug"] == post_data["slug"] assert item["post"]["title"] == marker assert item["time_ago"] is not None def test_devlog_works_for_guest_visitor(app_server): """Unauthenticated visitors can see the devlog section.""" session, _ = _member() project = _create_project(session) slug = project["slug"] or project["uid"] r = requests.get(f"{BASE_URL}/projects/{slug}", headers=JSON) assert r.status_code == 200, r.text[:300] body = r.json() assert "devlog_posts" in body assert body["devlog_posts"] == []