Three coordinated tweaks to the publication discovery surface: 1. Action-row CTA on /marketplace?tab=curated reads 'Submit a skill or plugin' instead of 'Submit a plugin'. Skills are first-class citizens of the curated shelf; the old wording made them feel like an afterthought. Same rename in the empty-state JS innerHTML so the two paths can't drift. 2. Curated guide page (/marketplace/guide/curated) expanded from a 4-line stub into a 3-step ordered list documenting the Named Curator handoff (find curator → handoff → publish + lifecycle). New '.guide-fastpath' callout block points users at the Flea Market when they want lighter review-bar / faster path. Primary CTA at the bottom of the curated guide now links to the flea guide too, so users who skim past the fast-path callout still see the escape hatch. 3. Flea guide page (/marketplace/guide/flea) expanded from a 3-line stub into a 4-step ordered list (package → upload via form → automated review → published). Documents the actual /store/new flow + the automated guardrails (manifest, content quality, prompt-injection scan) so users know what 'self-service' actually means before they upload. Route titles updated to match: 'Submit a skill or plugin to Curated Marketplace'. New file: tests/test_web_marketplace_guide.py — three tests covering the CTA rename, the curated guide's structural elements (Named Curators lede, 3 steps, fastpath callout, primary-CTA href), and the flea guide's structural elements (4 steps, no fastpath asymmetry, /store/new primary CTA).
140 lines
5.2 KiB
Python
140 lines
5.2 KiB
Python
"""GET /marketplace/guide/{curated,flea} — submission flow guides.
|
|
|
|
Both routes are authed (`get_current_user` dependency). The curated guide
|
|
documents the Named Curator handoff and has a fast-path callout pointing
|
|
at the flea self-service guide; the flea guide documents the /store/new
|
|
upload flow. Together with the action-row CTA on /marketplace?tab=curated,
|
|
this trio is the discovery surface for "how do I get my plugin published".
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tempfile
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture
|
|
def fresh_db(monkeypatch):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
monkeypatch.setenv("DATA_DIR", tmp)
|
|
monkeypatch.setenv("TESTING", "1")
|
|
monkeypatch.setenv("JWT_SECRET_KEY", "test-jwt-secret-key-minimum-32-chars!!")
|
|
yield tmp
|
|
|
|
|
|
def _make_user_and_session(conn, email="u@example.com"):
|
|
from src.repositories.users import UserRepository
|
|
from app.auth.jwt import create_access_token
|
|
|
|
uid = str(uuid.uuid4())
|
|
UserRepository(conn).create(id=uid, email=email, name=email.split("@")[0])
|
|
return uid, create_access_token(user_id=uid, email=email)
|
|
|
|
|
|
def _client():
|
|
from fastapi.testclient import TestClient
|
|
from app.main import app
|
|
|
|
return TestClient(app)
|
|
|
|
|
|
def test_marketplace_curated_tab_cta_text(fresh_db):
|
|
"""The action-row CTA on /marketplace?tab=curated reads
|
|
'Submit a skill or plugin' (renamed from 'Submit a plugin' so skills
|
|
aren't an afterthought) and links to the curated guide. Empty-state
|
|
fallback in JS uses the same string so both surfaces stay in sync."""
|
|
from src.db import get_system_db, close_system_db
|
|
|
|
conn = get_system_db()
|
|
try:
|
|
_, sess = _make_user_and_session(conn)
|
|
finally:
|
|
conn.close()
|
|
close_system_db()
|
|
body = _client().get(
|
|
"/marketplace?tab=curated", cookies={"access_token": sess}
|
|
).text
|
|
|
|
# Action-row anchor — primary discovery path.
|
|
assert (
|
|
'<a class="btn btn-secondary" data-actions-for="curated" '
|
|
'href="/marketplace/guide/curated">Submit a skill or plugin</a>'
|
|
) in body
|
|
# Empty-state JS innerHTML — same string, no drift.
|
|
assert "Submit a skill or plugin →" in body
|
|
# Old wording must be gone — guards against partial rename.
|
|
assert ">Submit a plugin<" not in body
|
|
|
|
|
|
def test_marketplace_guide_curated_page(fresh_db):
|
|
"""Curated guide page documents the Named Curator handoff. Three-step
|
|
flow (find → handoff → publish) lives inside `.guide-steps`. The
|
|
fast-path callout points users at the flea guide as the lighter
|
|
review-bar alternative; the primary CTA at the bottom does the same
|
|
so users who skim past the callout still see the escape hatch."""
|
|
from src.db import get_system_db, close_system_db
|
|
|
|
conn = get_system_db()
|
|
try:
|
|
_, sess = _make_user_and_session(conn)
|
|
finally:
|
|
conn.close()
|
|
close_system_db()
|
|
resp = _client().get(
|
|
"/marketplace/guide/curated", cookies={"access_token": sess}
|
|
)
|
|
assert resp.status_code == 200
|
|
body = resp.text
|
|
|
|
# Title carries the new 'skill or plugin' wording.
|
|
assert "Submit a skill or plugin to Curated Marketplace" in body
|
|
# Lede surfaces the gatekeeping concept.
|
|
assert "Named Curators" in body
|
|
# Three-step ordered list under `.guide-steps`.
|
|
assert '<ol class="guide-steps">' in body
|
|
assert "Find a Curator" in body
|
|
assert "Hand off your skill or plugin" in body
|
|
assert "Curator publishes" in body
|
|
# Fast-path callout exists and the CTA inside it points at the flea
|
|
# guide (NOT /store/new directly — we want users to read the flea
|
|
# context before they upload).
|
|
assert '<div class="guide-fastpath">' in body
|
|
assert 'href="/marketplace/guide/flea"' in body
|
|
# Primary CTA at the bottom also surfaces the flea path.
|
|
assert 'class="primary" href="/marketplace/guide/flea"' in body
|
|
|
|
|
|
def test_marketplace_guide_flea_page(fresh_db):
|
|
"""Flea guide documents the /store/new self-service flow. Four-step
|
|
body (package → upload → automated review → published) replaces the
|
|
earlier stub. Primary CTA goes directly to /store/new since users
|
|
landing on the flea guide have already chosen the self-service path."""
|
|
from src.db import get_system_db, close_system_db
|
|
|
|
conn = get_system_db()
|
|
try:
|
|
_, sess = _make_user_and_session(conn)
|
|
finally:
|
|
conn.close()
|
|
close_system_db()
|
|
resp = _client().get(
|
|
"/marketplace/guide/flea", cookies={"access_token": sess}
|
|
)
|
|
assert resp.status_code == 200
|
|
body = resp.text
|
|
|
|
assert "Upload to Flea Market" in body
|
|
# Four-step ordered list (no fast-path callout on flea — it IS the
|
|
# fast path, the curated guide is what links here).
|
|
assert '<ol class="guide-steps">' in body
|
|
assert "Package what you" in body
|
|
assert "Upload via the form" in body
|
|
assert "Automated review" in body
|
|
assert "Published" in body
|
|
# Primary CTA goes straight to /store/new (flea is one click away
|
|
# from being live, no intermediate handoff).
|
|
assert 'class="primary" href="/store/new"' in body
|
|
# No fast-path callout here — sanity check the asymmetry sticks.
|
|
assert '<div class="guide-fastpath">' not in body
|