Full test suite is now the mandatory final validation; fix everything it surfaced
DevPlace CI / test (pull_request) Failing after 10m29s
DevPlace CI / test (pull_request) Failing after 10m29s
- Policy: every change ends with make test (all tiers, all tests) green; docs and agent guardrails updated accordingly - schema.py: ensure the full filtered/indexed column set of instances via get_table (ingress_slug, ports_json, container_gateway, slug, status, ...) so a partial first insert can never break the ingress proxy - docs_api: award body param location body -> json; gateway endpoints documented public -> user to match enforced auth - tests: missing get_table import (trash restore), audit read as admin (award), deterministic online-roster and leaderboard-cache handling, container visibility updated to the primary-admin-only rule, scoped AI usage heading selector past the hidden Tools nav links
This commit is contained in:
+4
-6
@@ -1,14 +1,12 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file documents detailed testing patterns, fixtures, and pitfalls for devplacepy/tests/ (Playwright + unit tests across api/e2e/unit tiers). Claude Code loads it automatically whenever a file under this directory is read or edited. Never run the test suite unless the user explicitly asks - validate with imports and manual checks instead.
|
||||
This file documents detailed testing patterns, fixtures, and pitfalls for devplacepy/tests/ (Playwright + unit tests across api/e2e/unit tiers). Claude Code loads it automatically whenever a file under this directory is read or edited. Every change ends with the full suite: `make test` runs all tests across all three tiers, and it must pass.
|
||||
|
||||
## Never run tests unless explicitly asked (critical guardrail)
|
||||
## Always run the full suite (critical guardrail)
|
||||
|
||||
**NEVER run tests unless specifically asked by user.** Not the full suite, not a single file - do not run any tests unless the user explicitly requests it. Validate each touched language manually instead: Python (compile + import), JS (parse / bracket matching), CSS (brace matching), HTML (tag matching). Zero tolerance. A clean `python -c "from devplacepy.main import app"` import is the baseline check.
|
||||
**Every change is validated by running the full test suite - ALL tests, all three tiers, via `make test`.** No tier skipped, no subset substituted for the whole. Preliminary per-language checks (Python compile + import, JS parse, CSS brace matching, HTML tag matching) and a clean `python -c "from devplacepy.main import app"` import come first, but they never replace the suite. A failure is a real signal and blocks completion until fixed.
|
||||
|
||||
**Never run tests unless the user explicitly asks for it.** Not the full suite, not a single file. Validate code with a clean import (`python -c "from devplacepy.main import app"`) and per-language manual checks instead.
|
||||
|
||||
If you do need to run a single test on explicit request: `python -m pytest tests/e2e/feed.py::test_name -v --tb=line -x`.
|
||||
During iteration a single test may be run for a fast feedback loop: `python -m pytest tests/e2e/feed.py::test_name -v --tb=line -x` - but the change is finished only when the complete suite is green.
|
||||
|
||||
## Fixture stack
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ def test_restore_revives_post_and_records_audit(seeded_db):
|
||||
|
||||
def test_restore_revoked_award_recomputes_stats(seeded_db):
|
||||
from datetime import datetime, timezone
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.database.awards import revoke_award
|
||||
from devplacepy.utils import generate_uid, make_combined_slug
|
||||
|
||||
|
||||
+12
-2
@@ -115,7 +115,6 @@ def _seed_news_audit_log():
|
||||
)
|
||||
refresh_snapshot()
|
||||
return uid
|
||||
from devplacepy.database import get_table
|
||||
_counter_polls = [0]
|
||||
AJAX_polls = {"X-Requested-With": "fetch"}
|
||||
def _session_polls():
|
||||
@@ -363,7 +362,18 @@ def test_feed_avatar_has_presence_dot(app_server):
|
||||
|
||||
|
||||
def test_feed_shows_online_now_section(app_server):
|
||||
s, name = _member()
|
||||
name = _unique("0onl")
|
||||
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,
|
||||
)
|
||||
html = s.get(f"{BASE_URL}/feed").text
|
||||
assert "online-users" in html
|
||||
assert "data-online-users-list" in html
|
||||
|
||||
+12
-4
@@ -87,15 +87,23 @@ def test_leaderboard_entries_carry_score_and_prestige(app_server, seeded_db):
|
||||
session, name = _signup()
|
||||
_reset_farm(name)
|
||||
_set_farm(name, prestige=50, total_harvests=20, ci_tier=3)
|
||||
response = requests.get(f"{BASE_URL}/game/leaderboard", headers=JSON)
|
||||
assert response.status_code == 200
|
||||
entries = response.json()["entries"]
|
||||
deadline = time.time() + 20
|
||||
while True:
|
||||
response = requests.get(f"{BASE_URL}/game/leaderboard", headers=JSON)
|
||||
assert response.status_code == 200
|
||||
entries = response.json()["entries"]
|
||||
mine = next(
|
||||
(entry for entry in entries if entry["username"] == name), None
|
||||
)
|
||||
if mine is not None or time.time() >= deadline:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
assert entries
|
||||
scores = [entry["score"] for entry in entries]
|
||||
assert scores == sorted(scores, reverse=True)
|
||||
for entry in entries:
|
||||
assert "score" in entry and "prestige" in entry
|
||||
mine = next(entry for entry in entries if entry["username"] == name)
|
||||
assert mine is not None
|
||||
assert mine["prestige"] == 50
|
||||
assert mine["score"] == economy.farm_score(store.get_farm(
|
||||
get_table("users").find_one(username=name)["uid"]
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
import time
|
||||
import requests
|
||||
from datetime import datetime, timezone
|
||||
from tests.conftest import BASE_URL
|
||||
from devplacepy.db_client import get_table
|
||||
from devplacepy.utils import generate_uid, make_combined_slug
|
||||
|
||||
JSON = {"Accept": "application/json"}
|
||||
_counter = [0]
|
||||
@@ -177,7 +175,7 @@ def test_valid_post_creates_pending_row_and_job(app_server):
|
||||
assert job and job.get("kind") == "award"
|
||||
|
||||
|
||||
def test_award_give_audit_recorded(app_server):
|
||||
def test_award_give_audit_recorded(app_server, seeded_db):
|
||||
giver, _ = _signup()
|
||||
_, receiver_name = _signup()
|
||||
r = giver.post(
|
||||
@@ -186,5 +184,6 @@ def test_award_give_audit_recorded(app_server):
|
||||
json={"description": "Audit me"},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
entry = _audit_find(giver, "award.give", _uid(receiver_name))
|
||||
admin = _login(seeded_db, "alice")
|
||||
entry = _audit_find(admin, "award.give", _uid(receiver_name))
|
||||
assert entry is not None
|
||||
@@ -3,7 +3,7 @@
|
||||
import time
|
||||
import requests
|
||||
from tests.conftest import BASE_URL
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.database import get_primary_admin_uid, get_table, invalidate_admins_cache
|
||||
from devplacepy.utils import clear_user_cache
|
||||
|
||||
_counter_cv = [0]
|
||||
@@ -70,11 +70,14 @@ def test_admin_private_project_containers_hidden_from_other_admin(app_server):
|
||||
assert _containers_data_status(owner_key, slug) == 200
|
||||
|
||||
|
||||
def test_member_private_project_containers_visible_to_admin(app_server):
|
||||
def test_member_private_project_containers_primary_admin_only(app_server):
|
||||
_, _, owner_key = _signup()
|
||||
_, _, admin_key = _make_admin()
|
||||
slug = _create_project(owner_key, "Member Container Hidden", is_private=True)
|
||||
assert _containers_data_status(admin_key, slug) == 200
|
||||
assert _containers_data_status(admin_key, slug) == 404
|
||||
invalidate_admins_cache()
|
||||
primary_key = get_table("users").find_one(uid=get_primary_admin_uid())["api_key"]
|
||||
assert _containers_data_status(primary_key, slug) == 200
|
||||
|
||||
|
||||
def test_public_project_containers_visible_to_any_admin(app_server):
|
||||
|
||||
@@ -68,4 +68,4 @@ def _seed_target_user():
|
||||
def test_admin_ai_usage_page_loads(alice):
|
||||
page, _ = alice
|
||||
page.goto(f"{BASE_URL}/admin/ai-usage", wait_until="domcontentloaded")
|
||||
assert page.is_visible("text=AI usage") or page.is_visible("text=AI Usage")
|
||||
page.locator("h2:has-text('AI usage')").wait_for(state="visible")
|
||||
|
||||
Reference in New Issue
Block a user