This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
"""Shared mattermost-lts test helpers.
|
||||
|
||||
mattermost lets the FIRST user be created unauthenticated (and makes them system admin); after that,
|
||||
open signups are disabled (`api.user.create_user.no_open_server`). Several functional tests share one
|
||||
per-run deployment (the custom tier), so they cannot each create "the first user." Instead they all
|
||||
bootstrap ONE deterministic admin: whichever test runs first creates it (201, as the first user); the
|
||||
rest log in as the same admin. Subsequent (non-first) users are created via the admin token, which
|
||||
works regardless of the open-signup setting.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "runner"))
|
||||
from harness import http as harness_http # noqa: E402
|
||||
|
||||
ADMIN_EMAIL = "ccci-admin@ccci.example.com"
|
||||
ADMIN_USERNAME = "ccciadmin"
|
||||
ADMIN_PW = "Ccci-Test-Pw-2026!"
|
||||
|
||||
|
||||
def bearer(token: str) -> dict[str, str]:
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def bootstrap_admin(base: str) -> str:
|
||||
"""Return a system-admin session token for the shared deployment.
|
||||
|
||||
Create the deterministic admin as the first user if the server is fresh (201); otherwise it
|
||||
already exists (400/403) — log in as it. Either way we end with a valid admin token. RAISES if
|
||||
neither create nor login yields a usable session (a genuinely broken auth path)."""
|
||||
# Try to create the first user (unauthenticated; only succeeds on a fresh server).
|
||||
harness_http.http_post(
|
||||
f"{base}/users",
|
||||
data={"email": ADMIN_EMAIL, "username": ADMIN_USERNAME, "password": ADMIN_PW},
|
||||
timeout=30,
|
||||
)
|
||||
# Whether or not creation succeeded (it 4xxs if the admin already exists / signups closed), log in.
|
||||
status, _, hdrs = harness_http.post_with_headers(
|
||||
f"{base}/users/login", data={"login_id": ADMIN_EMAIL, "password": ADMIN_PW}, timeout=30
|
||||
)
|
||||
assert status == 200, f"admin login failed: HTTP {status}"
|
||||
token = hdrs.get("Token") or hdrs.get("token")
|
||||
assert token, f"admin login returned no Token header; headers={list(hdrs.keys())}"
|
||||
return token
|
||||
@@ -0,0 +1,79 @@
|
||||
"""mattermost-lts — Q4.5 recipe-specific functional test (plan §4.3: "create the app's primary
|
||||
object — a message — and read it back").
|
||||
|
||||
Exercises mattermost's core function end-to-end against the live per-run deploy, via the REST API:
|
||||
1. Bootstrap the FIRST user (a fresh mattermost server lets the first user be created unauthenticated
|
||||
and makes them system admin).
|
||||
2. Log in → capture the session token from the `Token` response header.
|
||||
3. Create a team, then an open channel in it.
|
||||
4. POST a message (a unique marker) to the channel.
|
||||
5. GET the post back by id and assert the message text round-trips intact.
|
||||
|
||||
NOT health-only: a mattermost whose DB/API/posting path is broken fails here even though `/` and
|
||||
`/api/v4/system/ping` return 200. The marker is unique per run so a stale/echoed response can't pass.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "runner"))
|
||||
import _mm # noqa: E402
|
||||
from harness import http as harness_http # noqa: E402
|
||||
|
||||
|
||||
def test_create_message_roundtrip(live_app):
|
||||
base = f"https://{live_app}/api/v4"
|
||||
uniq = uuid.uuid4().hex[:10]
|
||||
|
||||
# 1-2) Bootstrap the shared system admin (first user on a fresh server, else log in as it).
|
||||
# mattermost allows only ONE unauthenticated first-user creation, and several functional tests
|
||||
# share this deployment — so all bootstrap the same deterministic admin (see _mm.bootstrap_admin).
|
||||
auth = _mm.bearer(_mm.bootstrap_admin(base))
|
||||
|
||||
# 3) Create a team, then an open channel in it.
|
||||
status, team = harness_http.http_post(
|
||||
f"{base}/teams",
|
||||
data={"name": f"t{uniq}", "display_name": f"ccci {uniq}", "type": "O"},
|
||||
headers=auth,
|
||||
timeout=30,
|
||||
)
|
||||
assert (
|
||||
status in (200, 201) and isinstance(team, dict) and team.get("id")
|
||||
), f"team creation failed: HTTP {status}, body={team!r}"
|
||||
status, chan = harness_http.http_post(
|
||||
f"{base}/channels",
|
||||
data={
|
||||
"team_id": team["id"],
|
||||
"name": f"c{uniq}",
|
||||
"display_name": f"chan {uniq}",
|
||||
"type": "O",
|
||||
},
|
||||
headers=auth,
|
||||
timeout=30,
|
||||
)
|
||||
assert (
|
||||
status in (200, 201) and isinstance(chan, dict) and chan.get("id")
|
||||
), f"channel creation failed: HTTP {status}, body={chan!r}"
|
||||
|
||||
# 4) POST a unique marker message.
|
||||
marker = f"ccci-marker-{uniq}-roundtrip"
|
||||
status, post = harness_http.http_post(
|
||||
f"{base}/posts",
|
||||
data={"channel_id": chan["id"], "message": marker},
|
||||
headers=auth,
|
||||
timeout=30,
|
||||
)
|
||||
assert (
|
||||
status in (200, 201) and isinstance(post, dict) and post.get("id")
|
||||
), f"post creation failed: HTTP {status}, body={post!r}"
|
||||
|
||||
# 5) Read it back by id and assert the message survived the round-trip.
|
||||
status, got = harness_http.http_get(f"{base}/posts/{post['id']}", headers=auth, timeout=30)
|
||||
assert status == 200 and isinstance(got, dict), f"read-back failed: HTTP {status}, body={got!r}"
|
||||
assert (
|
||||
got.get("message") == marker
|
||||
), f"message did not round-trip: sent {marker!r}, got {got.get('message')!r}"
|
||||
@@ -0,0 +1,33 @@
|
||||
"""mattermost-lts — Phase-2 health_check (recipe-maintainer corpus has no parity test for this recipe).
|
||||
|
||||
Two real assertions on app state, not a bare root 200:
|
||||
1. The web app is served at `/` (200/302 — SPA shell or redirect toward login).
|
||||
2. The dedicated API liveness endpoint GET /api/v4/system/ping returns {"status":"OK"} — this
|
||||
proves the mattermost server process (not just Traefik) is up and its API router is live.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "runner"))
|
||||
from harness import http as harness_http # noqa: E402
|
||||
|
||||
|
||||
def test_root_serves(live_app):
|
||||
"""GET / → 200 or 302 (mattermost web app shell / login redirect)."""
|
||||
url = f"https://{live_app}/"
|
||||
status, _ = harness_http.retry_http_get(url, expect_status=(200, 302), max_wait=60, interval=3)
|
||||
assert status in (200, 302), f"GET {url} HTTP {status} (expected 200/302)"
|
||||
|
||||
|
||||
def test_system_ping_ok(live_app):
|
||||
"""GET /api/v4/system/ping → 200 with JSON {"status":"OK"} — the mattermost server's own
|
||||
liveness endpoint (distinguishes a live mattermost API from a Traefik fallback / dead backend)."""
|
||||
url = f"https://{live_app}/api/v4/system/ping"
|
||||
status, body = harness_http.retry_http_get(url, expect_status=200, max_wait=120, interval=3)
|
||||
assert status == 200, f"GET {url} HTTP {status} (expected 200)"
|
||||
assert (
|
||||
isinstance(body, dict) and body.get("status") == "OK"
|
||||
), f"/api/v4/system/ping did not report status=OK; got {body!r}"
|
||||
@@ -0,0 +1,110 @@
|
||||
"""mattermost-lts — 2nd recipe-specific functional test (Phase 2 P3): multi-user message visibility.
|
||||
|
||||
The defining behaviour of a team-chat platform is that a message one user posts is delivered to and
|
||||
readable by *another* user in the same channel — not just round-tripped by its own author (that is
|
||||
`test_create_message.py`). This exercises the real membership + post-delivery path end-to-end:
|
||||
|
||||
1. Bootstrap the shared system admin (user_a) → create team + open channel.
|
||||
2. user_a posts a unique marker message to the channel.
|
||||
3. Create a SECOND user (user_b) via the admin API; add user_b to the team + the channel.
|
||||
4. user_b logs in (its own session token) and GETs the channel's posts.
|
||||
5. Assert user_b sees user_a's marker message — cross-user delivery, not a self read-back.
|
||||
|
||||
Distinct code path from the single-user post round-trip (membership, ACL, multi-session post fetch).
|
||||
Real assertions on delivered app state; unique marker per run so a stale/echoed response can't pass.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "runner"))
|
||||
import _mm # noqa: E402
|
||||
from harness import http as harness_http # noqa: E402
|
||||
|
||||
|
||||
def _login(base: str, login_id: str) -> str:
|
||||
status, _, hdrs = harness_http.post_with_headers(
|
||||
f"{base}/users/login", data={"login_id": login_id, "password": _mm.ADMIN_PW}, timeout=30
|
||||
)
|
||||
assert status == 200, f"login {login_id} failed: HTTP {status}"
|
||||
token = hdrs.get("Token") or hdrs.get("token")
|
||||
assert token, f"login {login_id} returned no Token header; headers={list(hdrs.keys())}"
|
||||
return token
|
||||
|
||||
|
||||
def test_second_user_reads_first_users_message(live_app):
|
||||
base = f"https://{live_app}/api/v4"
|
||||
uniq = uuid.uuid4().hex[:10]
|
||||
|
||||
# 1) user_a = shared system admin; create team + open channel
|
||||
auth_a = _mm.bearer(_mm.bootstrap_admin(base))
|
||||
status, team = harness_http.http_post(
|
||||
f"{base}/teams",
|
||||
data={"name": f"t{uniq}", "display_name": f"ccci {uniq}", "type": "O"},
|
||||
headers=auth_a,
|
||||
timeout=30,
|
||||
)
|
||||
assert status in (200, 201) and team.get("id"), f"team create HTTP {status}: {team!r}"
|
||||
status, chan = harness_http.http_post(
|
||||
f"{base}/channels",
|
||||
data={
|
||||
"team_id": team["id"],
|
||||
"name": f"c{uniq}",
|
||||
"display_name": f"chan {uniq}",
|
||||
"type": "O",
|
||||
},
|
||||
headers=auth_a,
|
||||
timeout=30,
|
||||
)
|
||||
assert status in (200, 201) and chan.get("id"), f"channel create HTTP {status}: {chan!r}"
|
||||
|
||||
# 2) user_a posts a unique marker
|
||||
marker = f"ccci-multiuser-{uniq}"
|
||||
status, post = harness_http.http_post(
|
||||
f"{base}/posts",
|
||||
data={"channel_id": chan["id"], "message": marker},
|
||||
headers=auth_a,
|
||||
timeout=30,
|
||||
)
|
||||
assert status in (200, 201) and post.get("id"), f"post create HTTP {status}: {post!r}"
|
||||
|
||||
# 3) create user_b (admin API — works with open-signup off) + add to team + channel
|
||||
email_b = f"ccci{uniq}b@ccci.example.com"
|
||||
status, ub = harness_http.http_post(
|
||||
f"{base}/users",
|
||||
data={"email": email_b, "username": f"ccci{uniq}b", "password": _mm.ADMIN_PW},
|
||||
headers=auth_a,
|
||||
timeout=30,
|
||||
)
|
||||
assert status in (200, 201) and ub.get("id"), f"user_b create HTTP {status}: {ub!r}"
|
||||
status, _ = harness_http.http_post(
|
||||
f"{base}/teams/{team['id']}/members",
|
||||
data={"team_id": team["id"], "user_id": ub["id"]},
|
||||
headers=auth_a,
|
||||
timeout=30,
|
||||
)
|
||||
assert status in (200, 201), f"add user_b to team HTTP {status}"
|
||||
status, _ = harness_http.http_post(
|
||||
f"{base}/channels/{chan['id']}/members",
|
||||
data={"user_id": ub["id"]},
|
||||
headers=auth_a,
|
||||
timeout=30,
|
||||
)
|
||||
assert status in (200, 201), f"add user_b to channel HTTP {status}"
|
||||
|
||||
# 4) user_b logs in (own session) and reads the channel posts
|
||||
auth_b = _mm.bearer(_login(base, email_b))
|
||||
status, posts = harness_http.http_get(
|
||||
f"{base}/channels/{chan['id']}/posts", headers=auth_b, timeout=30
|
||||
)
|
||||
assert status == 200 and isinstance(posts, dict), f"user_b get posts HTTP {status}: {posts!r}"
|
||||
|
||||
# 5) user_b sees user_a's marker (cross-user delivery, not a self read-back)
|
||||
messages = [p.get("message") for p in (posts.get("posts") or {}).values()]
|
||||
assert (
|
||||
marker in messages
|
||||
), f"user_b did not see user_a's message {marker!r} in the channel; saw {messages!r}"
|
||||
Reference in New Issue
Block a user