Step 12 - completed
This commit is contained in:
@@ -1,52 +1,34 @@
|
||||
"""Tests for Step 10: Plugin Marketplace.
|
||||
"""Tests for Step 10+12: Plugin Marketplace (DB-backed).
|
||||
|
||||
Covers:
|
||||
- PluginRegistry: catalog management, filtering, sorting, install counts
|
||||
- PluginRegistry: catalog management, filtering, sorting, install counts (PostgreSQL)
|
||||
- ReviewQueue: pending queue, review decisions, manifest security checklist
|
||||
- RevenueShare: install event recording, earnings aggregation
|
||||
- RevenueShare: install event recording, earnings aggregation (PostgreSQL)
|
||||
- Route integration: tier gate, list/get/install/uninstall via TestClient
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from fastapi.testclient import TestClient
|
||||
from jose import jwt
|
||||
from unittest.mock import patch
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config.settings import settings
|
||||
from app.main import app
|
||||
from app.marketplace.plugin_registry import PluginRegistry
|
||||
from app.marketplace.plugin_review import ReviewQueue, validate_manifest
|
||||
from app.marketplace.revenue_share import RevenueShare
|
||||
from app.models import Plugin, PluginReview as PluginReviewModel, RevenueEvent
|
||||
from app.schemas import PluginManifest
|
||||
from tests.conftest import TEST_USER_IDS, auth_header
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _make_jwt(tier: str = "power", user_id: str | None = None) -> str:
|
||||
uid = user_id or str(uuid.uuid4())
|
||||
now = int(time.time())
|
||||
payload = {
|
||||
"sub": uid,
|
||||
"email": f"{uid[:8]}@example.com",
|
||||
"tier": tier,
|
||||
"exp": now + 3600,
|
||||
"iat": now,
|
||||
}
|
||||
return jwt.encode(payload, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM)
|
||||
|
||||
|
||||
def _auth(tier: str = "power") -> dict[str, str]:
|
||||
return {"Authorization": f"Bearer {_make_jwt(tier)}"}
|
||||
|
||||
|
||||
def _fresh_manifest(
|
||||
plugin_id: str | None = None,
|
||||
category: str = "productivity",
|
||||
@@ -67,118 +49,150 @@ def _fresh_manifest(
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PluginRegistry
|
||||
# PluginRegistry (DB-backed)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestPluginRegistry:
|
||||
"""Each test uses a fresh PluginRegistry instance to avoid catalog pollution."""
|
||||
"""Each test uses the conftest db_session fixture with a fresh in-memory DB."""
|
||||
|
||||
@pytest.fixture
|
||||
def reg(self) -> PluginRegistry:
|
||||
return PluginRegistry()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_seed_plugins_are_approved(self, reg: PluginRegistry) -> None:
|
||||
result = await reg.list_plugins()
|
||||
async def test_seed_plugins_are_listed(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
result = await reg.list_plugins(db_session)
|
||||
assert result.total == 3
|
||||
assert all(p.id.startswith("plugin-") for p in result.plugins)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_approved_only(self, reg: PluginRegistry) -> None:
|
||||
async def test_list_approved_only(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
manifest = _fresh_manifest()
|
||||
await reg.submit_plugin(manifest, "plugins/key.zip")
|
||||
result = await reg.list_plugins()
|
||||
await reg.submit_plugin(db_session, manifest, "plugins/key.zip")
|
||||
result = await reg.list_plugins(db_session)
|
||||
ids = [p.id for p in result.plugins]
|
||||
assert manifest.id not in ids # still pending
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_filter_by_category(self, reg: PluginRegistry) -> None:
|
||||
result = await reg.list_plugins(category="communication")
|
||||
async def test_list_filter_by_category(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
result = await reg.list_plugins(db_session, category="communication")
|
||||
assert result.total == 1
|
||||
assert result.plugins[0].id == "plugin-slack-notify"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_filter_by_query(self, reg: PluginRegistry) -> None:
|
||||
result = await reg.list_plugins(query="time")
|
||||
async def test_list_filter_by_query(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
result = await reg.list_plugins(db_session, query="time")
|
||||
assert result.total == 1
|
||||
assert result.plugins[0].id == "plugin-time-tracker"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_sort_by_installs(self, reg: PluginRegistry) -> None:
|
||||
await reg.record_install("plugin-slack-notify")
|
||||
await reg.record_install("plugin-slack-notify")
|
||||
result = await reg.list_plugins(sort="installs")
|
||||
async def test_list_sort_by_installs(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
await reg.record_install(db_session, "plugin-slack-notify")
|
||||
await reg.record_install(db_session, "plugin-slack-notify")
|
||||
result = await reg.list_plugins(db_session, sort="installs")
|
||||
assert result.plugins[0].id == "plugin-slack-notify"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_plugin_found(self, reg: PluginRegistry) -> None:
|
||||
entry = await reg.get_plugin("plugin-github-sync")
|
||||
async def test_get_plugin_found(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
entry = await reg.get_plugin(db_session, "plugin-github-sync")
|
||||
assert entry is not None
|
||||
assert entry["manifest"].id == "plugin-github-sync"
|
||||
assert "install_count" in entry
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_plugin_not_found(self, reg: PluginRegistry) -> None:
|
||||
entry = await reg.get_plugin("no-such-plugin")
|
||||
async def test_get_plugin_not_found(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession
|
||||
) -> None:
|
||||
entry = await reg.get_plugin(db_session, "no-such-plugin")
|
||||
assert entry is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_submit_sets_pending(self, reg: PluginRegistry) -> None:
|
||||
async def test_submit_sets_pending(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession
|
||||
) -> None:
|
||||
manifest = _fresh_manifest()
|
||||
plugin_id = await reg.submit_plugin(manifest, "key.zip")
|
||||
plugin_id = await reg.submit_plugin(db_session, manifest, "key.zip")
|
||||
assert plugin_id == manifest.id
|
||||
assert reg._catalog[plugin_id]["status"] == "pending_review"
|
||||
result = await db_session.execute(select(Plugin).where(Plugin.id == plugin_id))
|
||||
row = result.scalar_one()
|
||||
assert row.status == "pending_review"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_approve_makes_visible(self, reg: PluginRegistry) -> None:
|
||||
async def test_approve_makes_visible(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession
|
||||
) -> None:
|
||||
manifest = _fresh_manifest()
|
||||
await reg.submit_plugin(manifest, "key.zip")
|
||||
await reg.approve_plugin(manifest.id)
|
||||
result = await reg.list_plugins()
|
||||
await reg.submit_plugin(db_session, manifest, "key.zip")
|
||||
await reg.approve_plugin(db_session, manifest.id)
|
||||
result = await reg.list_plugins(db_session)
|
||||
assert manifest.id in [p.id for p in result.plugins]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reject_stores_reason(self, reg: PluginRegistry) -> None:
|
||||
async def test_reject_stores_reason(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession
|
||||
) -> None:
|
||||
manifest = _fresh_manifest()
|
||||
await reg.submit_plugin(manifest, "key.zip")
|
||||
await reg.reject_plugin(manifest.id, reason="Unsafe permissions")
|
||||
assert reg._catalog[manifest.id]["status"] == "rejected"
|
||||
assert reg._catalog[manifest.id]["rejection_reason"] == "Unsafe permissions"
|
||||
result = await reg.list_plugins()
|
||||
assert manifest.id not in [p.id for p in result.plugins]
|
||||
await reg.submit_plugin(db_session, manifest, "key.zip")
|
||||
await reg.reject_plugin(db_session, manifest.id, reason="Unsafe permissions")
|
||||
result = await db_session.execute(select(Plugin).where(Plugin.id == manifest.id))
|
||||
row = result.scalar_one()
|
||||
assert row.status == "rejected"
|
||||
assert row.rejection_reason == "Unsafe permissions"
|
||||
listed = await reg.list_plugins(db_session)
|
||||
assert manifest.id not in [p.id for p in listed.plugins]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_approve_unknown_raises_key_error(self, reg: PluginRegistry) -> None:
|
||||
async def test_approve_unknown_raises_key_error(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession
|
||||
) -> None:
|
||||
with pytest.raises(KeyError):
|
||||
await reg.approve_plugin("ghost-plugin")
|
||||
await reg.approve_plugin(db_session, "ghost-plugin")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_install_increments_count(self, reg: PluginRegistry) -> None:
|
||||
await reg.record_install("plugin-github-sync")
|
||||
entry = await reg.get_plugin("plugin-github-sync")
|
||||
async def test_record_install_increments_count(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
await reg.record_install(db_session, "plugin-github-sync")
|
||||
entry = await reg.get_plugin(db_session, "plugin-github-sync")
|
||||
assert entry is not None
|
||||
assert entry["install_count"] == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_uninstall_decrements_count(self, reg: PluginRegistry) -> None:
|
||||
await reg.record_install("plugin-github-sync")
|
||||
await reg.record_install("plugin-github-sync")
|
||||
await reg.record_uninstall("plugin-github-sync")
|
||||
entry = await reg.get_plugin("plugin-github-sync")
|
||||
async def test_record_uninstall_decrements_count(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
await reg.record_install(db_session, "plugin-github-sync")
|
||||
await reg.record_install(db_session, "plugin-github-sync")
|
||||
await reg.record_uninstall(db_session, "plugin-github-sync")
|
||||
entry = await reg.get_plugin(db_session, "plugin-github-sync")
|
||||
assert entry is not None
|
||||
assert entry["install_count"] == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_uninstall_floors_at_zero(self, reg: PluginRegistry) -> None:
|
||||
await reg.record_uninstall("plugin-github-sync") # already 0
|
||||
entry = await reg.get_plugin("plugin-github-sync")
|
||||
async def test_record_uninstall_floors_at_zero(
|
||||
self, reg: PluginRegistry, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
await reg.record_uninstall(db_session, "plugin-github-sync")
|
||||
entry = await reg.get_plugin(db_session, "plugin-github-sync")
|
||||
assert entry is not None
|
||||
assert entry["install_count"] == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ReviewQueue
|
||||
# ReviewQueue (DB-backed)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -188,37 +202,47 @@ class TestReviewQueue:
|
||||
return PluginRegistry()
|
||||
|
||||
@pytest.fixture
|
||||
def queue(self, reg: PluginRegistry) -> ReviewQueue:
|
||||
# Patch the 'registry' name as bound inside plugin_review.py
|
||||
with patch("app.marketplace.plugin_review.registry", reg):
|
||||
yield ReviewQueue()
|
||||
def queue(self) -> ReviewQueue:
|
||||
return ReviewQueue()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_pending_returns_submitted_plugins(
|
||||
self, reg: PluginRegistry, queue: ReviewQueue
|
||||
self, reg: PluginRegistry, queue: ReviewQueue, db_session: AsyncSession
|
||||
) -> None:
|
||||
manifest = _fresh_manifest()
|
||||
await reg.submit_plugin(manifest, "key.zip")
|
||||
pending = await queue.get_pending()
|
||||
await reg.submit_plugin(db_session, manifest, "key.zip")
|
||||
pending = await queue.get_pending(db_session)
|
||||
assert any(p["plugin_id"] == manifest.id for p in pending)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_submit_review_approved(
|
||||
self, reg: PluginRegistry, queue: ReviewQueue
|
||||
self, reg: PluginRegistry, queue: ReviewQueue, db_session: AsyncSession
|
||||
) -> None:
|
||||
manifest = _fresh_manifest()
|
||||
await reg.submit_plugin(manifest, "key.zip")
|
||||
await queue.submit_review(manifest.id, "reviewer-1", "approved", "Looks good")
|
||||
assert reg._catalog[manifest.id]["status"] == "approved"
|
||||
await reg.submit_plugin(db_session, manifest, "key.zip")
|
||||
await queue.submit_review(db_session, manifest.id, TEST_USER_IDS["power"], "approved", "Looks good")
|
||||
result = await db_session.execute(select(Plugin).where(Plugin.id == manifest.id))
|
||||
row = result.scalar_one()
|
||||
assert row.status == "approved"
|
||||
# Check review row was persisted
|
||||
review_result = await db_session.execute(
|
||||
select(PluginReviewModel).where(PluginReviewModel.plugin_id == manifest.id)
|
||||
)
|
||||
review = review_result.scalar_one()
|
||||
assert review.decision == "approved"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_submit_review_rejected(
|
||||
self, reg: PluginRegistry, queue: ReviewQueue
|
||||
self, reg: PluginRegistry, queue: ReviewQueue, db_session: AsyncSession
|
||||
) -> None:
|
||||
manifest = _fresh_manifest()
|
||||
await reg.submit_plugin(manifest, "key.zip")
|
||||
await queue.submit_review(manifest.id, "reviewer-1", "rejected", "Bad permissions")
|
||||
assert reg._catalog[manifest.id]["status"] == "rejected"
|
||||
await reg.submit_plugin(db_session, manifest, "key.zip")
|
||||
await queue.submit_review(
|
||||
db_session, manifest.id, TEST_USER_IDS["power"], "rejected", "Bad permissions"
|
||||
)
|
||||
result = await db_session.execute(select(Plugin).where(Plugin.id == manifest.id))
|
||||
row = result.scalar_one()
|
||||
assert row.status == "rejected"
|
||||
|
||||
def test_validate_manifest_ok(self) -> None:
|
||||
manifest = _fresh_manifest(permissions=["read:tasks", "write:notes"])
|
||||
@@ -241,65 +265,66 @@ class TestReviewQueue:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# RevenueShare
|
||||
# RevenueShare (DB-backed)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestRevenueShare:
|
||||
@pytest.fixture
|
||||
def reg(self) -> PluginRegistry:
|
||||
return PluginRegistry()
|
||||
|
||||
@pytest.fixture
|
||||
def rs(self, reg: PluginRegistry) -> RevenueShare:
|
||||
# Patch the 'registry' name as bound inside revenue_share.py
|
||||
with patch("app.marketplace.revenue_share.registry", reg):
|
||||
yield RevenueShare()
|
||||
def rs(self) -> RevenueShare:
|
||||
return RevenueShare()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_install_free_plugin(
|
||||
self, reg: PluginRegistry, rs: RevenueShare
|
||||
self, rs: RevenueShare, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
await rs.record_install("plugin-github-sync", "user-1", amount_cents=0)
|
||||
assert len(rs._events) == 1
|
||||
assert rs._events[0]["developer_share_cents"] == 0
|
||||
await rs.record_install(db_session, "plugin-github-sync", TEST_USER_IDS["power"], amount_cents=0)
|
||||
result = await db_session.execute(
|
||||
select(RevenueEvent).where(RevenueEvent.plugin_id == "plugin-github-sync")
|
||||
)
|
||||
event = result.scalar_one()
|
||||
assert event.developer_share_cents == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_install_paid_plugin_no_stripe(
|
||||
self, reg: PluginRegistry, rs: RevenueShare
|
||||
self, rs: RevenueShare, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
# No STRIPE_SECRET_KEY configured in test env — should not crash
|
||||
await rs.record_install("plugin-slack-notify", "user-2", amount_cents=499)
|
||||
assert len(rs._events) == 1
|
||||
assert rs._events[0]["amount_cents"] == 499
|
||||
assert rs._events[0]["developer_share_cents"] == int(499 * 0.70)
|
||||
await rs.record_install(
|
||||
db_session, "plugin-slack-notify", TEST_USER_IDS["pro"], amount_cents=499
|
||||
)
|
||||
result = await db_session.execute(
|
||||
select(RevenueEvent).where(RevenueEvent.plugin_id == "plugin-slack-notify")
|
||||
)
|
||||
event = result.scalar_one()
|
||||
assert event.amount_cents == 499
|
||||
assert event.developer_share_cents == int(499 * 0.70)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_record_install_increments_registry_count(
|
||||
self, reg: PluginRegistry, rs: RevenueShare
|
||||
self, rs: RevenueShare, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
await rs.record_install("plugin-github-sync", "user-1", amount_cents=0)
|
||||
entry = await reg.get_plugin("plugin-github-sync")
|
||||
reg = PluginRegistry()
|
||||
await rs.record_install(db_session, "plugin-github-sync", TEST_USER_IDS["power"], amount_cents=0)
|
||||
entry = await reg.get_plugin(db_session, "plugin-github-sync")
|
||||
assert entry is not None
|
||||
assert entry["install_count"] == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_earnings_empty(
|
||||
self, reg: PluginRegistry, rs: RevenueShare
|
||||
self, rs: RevenueShare, db_session: AsyncSession
|
||||
) -> None:
|
||||
result = await rs.get_earnings("unknown-dev")
|
||||
result = await rs.get_earnings(db_session, "unknown-dev")
|
||||
assert result["total_installs"] == 0
|
||||
assert result["total_revenue_cents"] == 0
|
||||
assert result["developer_share_cents"] == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_earnings_aggregates(
|
||||
self, reg: PluginRegistry, rs: RevenueShare
|
||||
self, rs: RevenueShare, db_session: AsyncSession, seed_plugins: list[Plugin]
|
||||
) -> None:
|
||||
# "Adiuva" is the author of the seeded plugins
|
||||
await rs.record_install("plugin-slack-notify", "u1", amount_cents=499)
|
||||
await rs.record_install("plugin-slack-notify", "u2", amount_cents=499)
|
||||
result = await rs.get_earnings("Adiuva")
|
||||
await rs.record_install(db_session, "plugin-slack-notify", TEST_USER_IDS["power"], amount_cents=499)
|
||||
await rs.record_install(db_session, "plugin-slack-notify", TEST_USER_IDS["pro"], amount_cents=499)
|
||||
result = await rs.get_earnings(db_session, "Adiuva")
|
||||
assert result["total_installs"] == 2
|
||||
assert result["total_revenue_cents"] == 998
|
||||
assert result["developer_share_cents"] == int(499 * 0.70) * 2
|
||||
@@ -311,77 +336,67 @@ class TestRevenueShare:
|
||||
|
||||
|
||||
class TestPluginRoutes:
|
||||
def test_list_plugins_requires_power_tier(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.get("/api/v1/plugins", headers=_auth("free"))
|
||||
def test_list_plugins_requires_power_tier(self, client, seed_plugins) -> None:
|
||||
resp = client.get("/api/v1/plugins", headers=auth_header("free"))
|
||||
assert resp.status_code == 403
|
||||
|
||||
def test_list_plugins_pro_tier_blocked(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.get("/api/v1/plugins", headers=_auth("pro"))
|
||||
def test_list_plugins_pro_tier_blocked(self, client, seed_plugins) -> None:
|
||||
resp = client.get("/api/v1/plugins", headers=auth_header("pro"))
|
||||
assert resp.status_code == 403
|
||||
|
||||
def test_list_plugins_power_tier_ok(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.get("/api/v1/plugins", headers=_auth("power"))
|
||||
def test_list_plugins_power_tier_ok(self, client, seed_plugins) -> None:
|
||||
resp = client.get("/api/v1/plugins", headers=auth_header("power"))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert "plugins" in data
|
||||
assert data["total"] >= 3
|
||||
assert data["total"] == 3
|
||||
|
||||
def test_list_plugins_team_tier_ok(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.get("/api/v1/plugins", headers=_auth("team"))
|
||||
def test_list_plugins_team_tier_ok(self, client, seed_plugins) -> None:
|
||||
resp = client.get("/api/v1/plugins", headers=auth_header("team"))
|
||||
assert resp.status_code == 200
|
||||
|
||||
def test_get_plugin_found(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.get("/api/v1/plugins/plugin-github-sync", headers=_auth())
|
||||
def test_get_plugin_found(self, client, seed_plugins) -> None:
|
||||
resp = client.get("/api/v1/plugins/plugin-github-sync", headers=auth_header())
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["plugin"]["id"] == "plugin-github-sync"
|
||||
assert "install_count" in data
|
||||
|
||||
def test_get_plugin_not_found(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.get("/api/v1/plugins/no-such-plugin", headers=_auth())
|
||||
def test_get_plugin_not_found(self, client, seed_plugins) -> None:
|
||||
resp = client.get("/api/v1/plugins/no-such-plugin", headers=auth_header())
|
||||
assert resp.status_code == 404
|
||||
|
||||
def test_install_plugin_free(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.post(
|
||||
"/api/v1/plugins/plugin-github-sync/install",
|
||||
json={"plugin_id": "plugin-github-sync"},
|
||||
headers=_auth(),
|
||||
)
|
||||
def test_install_plugin_free(self, client, seed_plugins) -> None:
|
||||
resp = client.post(
|
||||
"/api/v1/plugins/plugin-github-sync/install",
|
||||
json={"plugin_id": "plugin-github-sync"},
|
||||
headers=auth_header(),
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["ok"] is True
|
||||
assert "download_url" in data
|
||||
|
||||
def test_install_plugin_not_found(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.post(
|
||||
"/api/v1/plugins/ghost/install",
|
||||
json={"plugin_id": "ghost"},
|
||||
headers=_auth(),
|
||||
)
|
||||
def test_install_plugin_not_found(self, client, seed_plugins) -> None:
|
||||
resp = client.post(
|
||||
"/api/v1/plugins/ghost/install",
|
||||
json={"plugin_id": "ghost"},
|
||||
headers=auth_header(),
|
||||
)
|
||||
assert resp.status_code == 404
|
||||
|
||||
def test_uninstall_plugin_ok(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.delete(
|
||||
"/api/v1/plugins/plugin-github-sync/install",
|
||||
headers=_auth(),
|
||||
)
|
||||
def test_uninstall_plugin_ok(self, client, seed_plugins) -> None:
|
||||
resp = client.delete(
|
||||
"/api/v1/plugins/plugin-github-sync/install",
|
||||
headers=auth_header(),
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["ok"] is True
|
||||
|
||||
def test_install_requires_power_tier(self) -> None:
|
||||
with TestClient(app) as client:
|
||||
resp = client.post(
|
||||
"/api/v1/plugins/plugin-github-sync/install",
|
||||
json={"plugin_id": "plugin-github-sync"},
|
||||
headers=_auth("free"),
|
||||
)
|
||||
def test_install_requires_power_tier(self, client, seed_plugins) -> None:
|
||||
resp = client.post(
|
||||
"/api/v1/plugins/plugin-github-sync/install",
|
||||
json={"plugin_id": "plugin-github-sync"},
|
||||
headers=auth_header("free"),
|
||||
)
|
||||
assert resp.status_code == 403
|
||||
|
||||
Reference in New Issue
Block a user