Compare commits
4 commits
dc508d7197
...
3458122537
| Author | SHA1 | Date | |
|---|---|---|---|
| 3458122537 | |||
| d2aa169dfb | |||
| c1d6e53ff3 | |||
| 2f790b1a69 |
13 changed files with 353 additions and 26 deletions
|
|
@ -2,7 +2,7 @@
|
|||
# Auto-generated by the setup wizard, or fill in manually.
|
||||
# NEVER commit .env to git.
|
||||
|
||||
STREAMLIT_PORT=8501
|
||||
STREAMLIT_PORT=8502
|
||||
OLLAMA_PORT=11434
|
||||
VLLM_PORT=8000
|
||||
SEARXNG_PORT=8888
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ full instructions.
|
|||
```bash
|
||||
git clone https://git.opensourcesolarpunk.com/pyr0ball/peregrine.git
|
||||
cd peregrine
|
||||
./setup.sh # installs deps, activates git hooks
|
||||
./install.sh # installs deps, activates git hooks
|
||||
./manage.sh start
|
||||
```
|
||||
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -45,7 +45,7 @@ endif
|
|||
PROFILE_ARG := $(if $(filter remote,$(PROFILE)),,--profile $(PROFILE))
|
||||
|
||||
setup: ## Install dependencies (Docker or Podman + NVIDIA toolkit)
|
||||
@bash setup.sh
|
||||
@bash install.sh
|
||||
|
||||
preflight: ## Check ports + system resources; write .env
|
||||
@$(PYTHON) scripts/preflight.py
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ make start PROFILE=single-gpu
|
|||
|
||||
**3.** Open http://localhost:8501 — the setup wizard guides you through the rest.
|
||||
|
||||
> **macOS / Apple Silicon:** Docker Desktop must be running. For Metal GPU-accelerated inference, install Ollama natively before starting — `setup.sh` will prompt you to do this. See [Apple Silicon GPU](#apple-silicon-gpu) below.
|
||||
> **macOS / Apple Silicon:** Docker Desktop must be running. For Metal GPU-accelerated inference, install Ollama natively before starting — `install.sh` will prompt you to do this. See [Apple Silicon GPU](#apple-silicon-gpu) below.
|
||||
> **Windows:** Not supported — use WSL2 with Ubuntu.
|
||||
|
||||
### Installing to `/opt` or other system directories
|
||||
|
|
@ -103,7 +103,7 @@ After `./manage.sh setup`, log out and back in for docker group membership to ta
|
|||
|
||||
Docker Desktop on macOS runs in a Linux VM — it cannot access the Apple GPU. Metal-accelerated inference requires Ollama to run **natively** on the host.
|
||||
|
||||
`setup.sh` handles this automatically: it offers to install Ollama via Homebrew, starts it as a background service, and explains what happens next. If Ollama is running on port 11434 when you start Peregrine, preflight detects it, stubs out the Docker Ollama container, and routes inference through the native process — which uses Metal automatically.
|
||||
`install.sh` handles this automatically: it offers to install Ollama via Homebrew, starts it as a background service, and explains what happens next. If Ollama is running on port 11434 when you start Peregrine, preflight detects it, stubs out the Docker Ollama container, and routes inference through the native process — which uses Metal automatically.
|
||||
|
||||
To do it manually:
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ PEREGRINE_ROOT = Path("/Library/Development/CircuitForge/peregrine")
|
|||
if str(PEREGRINE_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PEREGRINE_ROOT))
|
||||
|
||||
from circuitforge_core.api import make_feedback_router as _make_feedback_router # noqa: E402
|
||||
from circuitforge_core.config.settings import load_env as _load_env # noqa: E402
|
||||
from scripts.credential_store import get_credential, set_credential, delete_credential # noqa: E402
|
||||
|
||||
|
|
@ -54,6 +55,14 @@ app.add_middleware(
|
|||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
_feedback_router = _make_feedback_router(
|
||||
repo="Circuit-Forge/peregrine",
|
||||
product="peregrine",
|
||||
demo_mode_fn=lambda: (
|
||||
_CLOUD_MODE or os.environ.get("DEMO_MODE", "").lower() in ("1", "true", "yes")
|
||||
),
|
||||
)
|
||||
app.include_router(_feedback_router, prefix="/api/feedback")
|
||||
|
||||
_log = logging.getLogger("peregrine.session")
|
||||
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ Shipped in v0.4.0. Ongoing maintenance and known decisions:
|
|||
|
||||
## Container Runtime
|
||||
|
||||
- ~~**Podman support**~~ — ✅ Done: `Makefile` auto-detects `docker compose` / `podman compose` / `podman-compose`; `compose.podman-gpu.yml` CDI override for GPU profiles; `setup.sh` detects existing Podman and skips Docker install.
|
||||
- ~~**Podman support**~~ — ✅ Done: `Makefile` auto-detects `docker compose` / `podman compose` / `podman-compose`; `compose.podman-gpu.yml` CDI override for GPU profiles; `install.sh` detects existing Podman and skips Docker install.
|
||||
- **FastAPI migration path** — When concurrent-user scale demands it: port Streamlit pages to FastAPI + React/HTMX, keep `scripts/` layer unchanged, replace daemon threads with Celery + Redis. The `scripts/` separation already makes this clean.
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ This page walks through a full Peregrine installation from scratch.
|
|||
## Prerequisites
|
||||
|
||||
- **Git** — to clone the repository
|
||||
- **Internet connection** — `setup.sh` downloads Docker and other dependencies
|
||||
- **Internet connection** — `install.sh` downloads Docker and other dependencies
|
||||
- **Operating system**: Ubuntu/Debian, Fedora/RHEL, Arch Linux, or macOS (with Docker Desktop)
|
||||
|
||||
!!! warning "Windows"
|
||||
|
|
@ -18,19 +18,19 @@ This page walks through a full Peregrine installation from scratch.
|
|||
## Step 1 — Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://git.circuitforge.io/circuitforge/peregrine
|
||||
git clone https://git.opensourcesolarpunk.com/Circuit-Forge/peregrine
|
||||
cd peregrine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Run setup.sh
|
||||
## Step 2 — Run install.sh
|
||||
|
||||
```bash
|
||||
bash setup.sh
|
||||
bash install.sh
|
||||
```
|
||||
|
||||
`setup.sh` performs the following automatically:
|
||||
`install.sh` performs the following automatically:
|
||||
|
||||
1. **Detects your platform** (Ubuntu/Debian, Fedora/RHEL, Arch, macOS)
|
||||
2. **Installs Git** if not already present
|
||||
|
|
@ -40,10 +40,10 @@ bash setup.sh
|
|||
6. **Creates `.env` from `.env.example`** — edit `.env` to customise ports and model storage paths before starting
|
||||
|
||||
!!! note "macOS"
|
||||
`setup.sh` installs Docker Desktop via Homebrew (`brew install --cask docker`) then exits. Open Docker Desktop, start it, then re-run the script.
|
||||
`install.sh` installs Docker Desktop via Homebrew (`brew install --cask docker`) then exits. Open Docker Desktop, start it, then re-run the script.
|
||||
|
||||
!!! note "GPU requirement"
|
||||
For GPU support, `nvidia-smi` must return output before you run `setup.sh`. Install your NVIDIA driver first. The Container Toolkit installation will fail silently if the driver is not present.
|
||||
For GPU support, `nvidia-smi` must return output before you run `install.sh`. Install your NVIDIA driver first. The Container Toolkit installation will fail silently if the driver is not present.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ The first-run wizard launches automatically. See [First-Run Wizard](first-run-wi
|
|||
Only NVIDIA GPUs are supported. AMD ROCm is not currently supported.
|
||||
|
||||
Requirements:
|
||||
- NVIDIA driver installed and `nvidia-smi` working before running `setup.sh`
|
||||
- NVIDIA driver installed and `nvidia-smi` working before running `install.sh`
|
||||
- CUDA 12.x recommended (CUDA 11.x may work but is untested)
|
||||
- Minimum 8 GB VRAM for `single-gpu` profile with default models
|
||||
- For `dual-gpu`: GPU 0 is assigned to Ollama, GPU 1 to vLLM
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Peregrine automates the full job search lifecycle: discovery, matching, cover le
|
|||
# 1. Clone and install dependencies
|
||||
git clone https://git.circuitforge.io/circuitforge/peregrine
|
||||
cd peregrine
|
||||
bash setup.sh
|
||||
bash install.sh
|
||||
|
||||
# 2. Start Peregrine
|
||||
make start # no GPU, API-only
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ webhook_url: "https://discord.com/api/webhooks/..."
|
|||
|
||||
## .env
|
||||
|
||||
Docker port and path overrides. Created from `.env.example` by `setup.sh`. Gitignored.
|
||||
Docker port and path overrides. Created from `.env.example` by `install.sh`. Gitignored.
|
||||
|
||||
```bash
|
||||
# Ports (change if defaults conflict with existing services)
|
||||
|
|
|
|||
157
docs/reference/forgejo-feedback-schema.md
Normal file
157
docs/reference/forgejo-feedback-schema.md
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# Forgejo Feedback API — Schema & Bug Bot Setup
|
||||
|
||||
## API Endpoints Used
|
||||
|
||||
| Operation | Method | Endpoint |
|
||||
|-----------|--------|----------|
|
||||
| List labels | GET | `/repos/{owner}/{repo}/labels` |
|
||||
| Create label | POST | `/repos/{owner}/{repo}/labels` |
|
||||
| Create issue | POST | `/repos/{owner}/{repo}/issues` |
|
||||
| Upload attachment | POST | `/repos/{owner}/{repo}/issues/{index}/assets` |
|
||||
| Post comment | POST | `/repos/{owner}/{repo}/issues/{index}/comments` |
|
||||
|
||||
Base URL: `https://git.opensourcesolarpunk.com/api/v1`
|
||||
|
||||
---
|
||||
|
||||
## Issue Creation Payload
|
||||
|
||||
```json
|
||||
POST /repos/{owner}/{repo}/issues
|
||||
{
|
||||
"title": "string",
|
||||
"body": "markdown string",
|
||||
"labels": [1, 2, 3] // array of label IDs (not names)
|
||||
}
|
||||
```
|
||||
|
||||
Response (201):
|
||||
```json
|
||||
{
|
||||
"number": 42,
|
||||
"html_url": "https://git.opensourcesolarpunk.com/pyr0ball/peregrine/issues/42"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Issue Body Structure
|
||||
|
||||
The `build_issue_body()` function produces this markdown layout:
|
||||
|
||||
```markdown
|
||||
## 🐛 Bug | ✨ Feature Request | 💬 Other
|
||||
|
||||
<user description>
|
||||
|
||||
### Reproduction Steps ← bug type only, when repro provided
|
||||
|
||||
<repro steps>
|
||||
|
||||
### Context
|
||||
|
||||
- **page:** Home
|
||||
- **version:** v0.2.5-61-ga6d787f ← from `git describe`; "dev" inside Docker
|
||||
- **tier:** free | paid | premium
|
||||
- **llm_backend:** ollama | vllm | claude_code | ...
|
||||
- **os:** Linux-6.8.0-65-generic-x86_64-with-glibc2.39
|
||||
- **timestamp:** 2026-03-06T15:58:29Z
|
||||
|
||||
<details>
|
||||
<summary>App Logs (last 100 lines)</summary>
|
||||
|
||||
```
|
||||
... log content (PII masked) ...
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Recent Listings ← only when include_diag = True
|
||||
|
||||
- [Title @ Company](url)
|
||||
|
||||
---
|
||||
*Submitted by: Name <email>* ← only when attribution consent checked
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Screenshot Attachment
|
||||
|
||||
Screenshots are uploaded as issue assets, then embedded inline via a follow-up comment:
|
||||
|
||||
```markdown
|
||||
### Screenshot
|
||||
|
||||

|
||||
```
|
||||
|
||||
This keeps the issue body clean and puts the screenshot in a distinct comment.
|
||||
|
||||
---
|
||||
|
||||
## Labels
|
||||
|
||||
| Label | Color | Applied when |
|
||||
|-------|-------|-------------|
|
||||
| `beta-feedback` | `#0075ca` | Always |
|
||||
| `needs-triage` | `#e4e669` | Always |
|
||||
| `bug` | `#d73a4a` | Type = Bug |
|
||||
| `feature-request` | `#a2eeef` | Type = Feature Request |
|
||||
| `question` | `#d876e3` | Type = Other |
|
||||
|
||||
Labels are looked up by name on each submission; missing ones are auto-created via `_ensure_labels()`.
|
||||
|
||||
---
|
||||
|
||||
## Bug Bot Account Setup
|
||||
|
||||
The token currently bundled in `.env` is pyr0ball's personal token. For beta distribution,
|
||||
create a dedicated bot account so the token has limited scope and can be rotated independently.
|
||||
|
||||
### Why a bot account?
|
||||
|
||||
- Token gets bundled in beta testers' `.env` — shouldn't be tied to the repo owner's account
|
||||
- Bot can be limited to issue write only (cannot push code, see private repos, etc.)
|
||||
- Token rotation doesn't affect the owner's other integrations
|
||||
|
||||
### Steps (requires Forgejo admin panel — API admin access not available on this token)
|
||||
|
||||
1. **Create bot account** at `https://git.opensourcesolarpunk.com/-/admin/users/new`
|
||||
- Username: `peregrine-bot` (or `cf-bugbot`)
|
||||
- Email: a real address you control (e.g. `bot+peregrine@circuitforge.tech`)
|
||||
- Set a strong password (store in your password manager)
|
||||
- Check "Prohibit login" if you want a pure API-only account
|
||||
|
||||
2. **Add as collaborator** on `pyr0ball/peregrine`:
|
||||
- Settings → Collaborators → Add `peregrine-bot` with **Write** access
|
||||
- Write access is required to create labels; issue creation alone would need only Read+Comment
|
||||
|
||||
3. **Generate API token** (log in as the bot, or use admin impersonation):
|
||||
- User Settings → Applications → Generate New Token
|
||||
- Name: `peregrine-feedback`
|
||||
- Scopes: `issue` (write) — no repo code access needed
|
||||
- Copy the token — it won't be shown again
|
||||
|
||||
4. **Update environment**:
|
||||
```
|
||||
FORGEJO_API_TOKEN=<new bot token>
|
||||
FORGEJO_REPO=pyr0ball/peregrine
|
||||
FORGEJO_API_URL=https://git.opensourcesolarpunk.com/api/v1
|
||||
```
|
||||
Update both `.env` (dev machine) and any beta tester `.env` files.
|
||||
|
||||
5. **Verify** the bot can create issues:
|
||||
```bash
|
||||
curl -s -X POST https://git.opensourcesolarpunk.com/api/v1/repos/pyr0ball/peregrine/issues \
|
||||
-H "Authorization: token <bot-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"title":"[TEST] bot token check","body":"safe to close","labels":[]}'
|
||||
```
|
||||
Expected: HTTP 201 with `number` and `html_url` in response.
|
||||
|
||||
### Future: Heimdall token management
|
||||
|
||||
Once Heimdall is live, the bot token should be served by the license server rather than
|
||||
bundled in `.env`. The app fetches it at startup using the user's license key → token is
|
||||
never stored on disk and can be rotated server-side. Track as a future Heimdall feature.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
# setup.sh — Peregrine dependency installer
|
||||
# install.sh — Peregrine dependency installer
|
||||
# Installs Docker, Docker Compose v2, and (optionally) NVIDIA Container Toolkit.
|
||||
# Supports: Ubuntu/Debian, Fedora/RHEL/CentOS, Arch Linux, macOS (Homebrew).
|
||||
# Windows: not supported — use WSL2 with Ubuntu.
|
||||
|
|
@ -90,15 +90,11 @@ configure_git_safe_dir() {
|
|||
}
|
||||
|
||||
activate_git_hooks() {
|
||||
local repo_dir hooks_installer
|
||||
local repo_dir
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
hooks_installer="/Library/Development/CircuitForge/circuitforge-hooks/install.sh"
|
||||
if [[ -f "$hooks_installer" ]]; then
|
||||
bash "$hooks_installer" --quiet
|
||||
success "CircuitForge hooks activated (circuitforge-hooks)."
|
||||
elif [[ -d "$repo_dir/.githooks" ]]; then
|
||||
if [[ -d "$repo_dir/.githooks" ]]; then
|
||||
git -C "$repo_dir" config core.hooksPath .githooks
|
||||
success "Git hooks activated (.githooks/) — circuitforge-hooks not found, using local fallback."
|
||||
success "Git hooks activated (.githooks/)."
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -341,6 +337,31 @@ setup_env() {
|
|||
fi
|
||||
}
|
||||
|
||||
# ── License key (optional) ────────────────────────────────────────────────────
|
||||
capture_license_key() {
|
||||
[[ ! -t 0 ]] && return # skip in non-interactive installs (curl | bash)
|
||||
local env_file
|
||||
env_file="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.env"
|
||||
[[ ! -f "$env_file" ]] && return # setup_env() creates it; nothing to write into yet
|
||||
|
||||
echo ""
|
||||
info "License key (optional)"
|
||||
echo -e " Peregrine works without a key for personal self-hosted use."
|
||||
echo -e " Paid-tier users: enter your ${YELLOW}CFG-XXXX-…${NC} key to unlock cloud LLM and integrations."
|
||||
echo ""
|
||||
read -rp " CircuitForge license key [press Enter to skip]: " _key || true
|
||||
if [[ -n "$_key" ]]; then
|
||||
if echo "$_key" | grep -qE '^CFG-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'; then
|
||||
_update_env_key "$env_file" "CF_LICENSE_KEY" "$_key"
|
||||
_update_env_key "$env_file" "HEIMDALL_URL" "https://license.circuitforge.tech"
|
||||
success "License key saved — paid-tier features enabled."
|
||||
else
|
||||
warn "Key format looks wrong (expected CFG-XXXX-AAAA-BBBB-CCCC) — skipping."
|
||||
info "Add it manually to .env as CF_LICENSE_KEY= later."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Model weights storage ───────────────────────────────────────────────────────
|
||||
_update_env_key() {
|
||||
# Portable in-place key=value update for .env files (Linux + macOS).
|
||||
|
|
@ -416,8 +437,15 @@ main() {
|
|||
fi
|
||||
install_ollama_macos
|
||||
setup_env
|
||||
capture_license_key
|
||||
configure_model_paths
|
||||
|
||||
# Read the actual port from .env so next-steps reflects any customisation
|
||||
local _script_dir _port
|
||||
_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
_port="$(grep -E '^STREAMLIT_PORT=' "$_script_dir/.env" 2>/dev/null | cut -d= -f2-)"
|
||||
_port="${_port:-8502}"
|
||||
|
||||
echo ""
|
||||
success "All dependencies installed."
|
||||
echo ""
|
||||
|
|
@ -429,7 +457,7 @@ main() {
|
|||
else
|
||||
echo -e " ${YELLOW}./manage.sh start --profile cpu${NC} # local Ollama inference (CPU)"
|
||||
fi
|
||||
echo -e " 2. Open ${YELLOW}http://localhost:8501${NC} — the setup wizard will guide you"
|
||||
echo -e " 2. Open ${YELLOW}http://localhost:${_port}${NC} — the setup wizard will guide you"
|
||||
echo -e " (Tip: edit ${YELLOW}.env${NC} any time to adjust ports or model paths)"
|
||||
echo ""
|
||||
if groups "$USER" 2>/dev/null | grep -q docker; then
|
||||
|
|
@ -84,7 +84,7 @@ case "$CMD" in
|
|||
|
||||
setup)
|
||||
info "Running dependency installer..."
|
||||
bash setup.sh
|
||||
bash install.sh
|
||||
;;
|
||||
|
||||
preflight)
|
||||
|
|
|
|||
133
tests/test_dev_api_feedback.py
Normal file
133
tests/test_dev_api_feedback.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
"""Tests for the /api/feedback routes in dev_api."""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(monkeypatch):
|
||||
monkeypatch.delenv("CLOUD_MODE", raising=False)
|
||||
monkeypatch.delenv("DEMO_MODE", raising=False)
|
||||
monkeypatch.delenv("FORGEJO_API_TOKEN", raising=False)
|
||||
from dev_api import app
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /api/feedback/status
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_status_disabled_when_no_token(client):
|
||||
"""Status is disabled when FORGEJO_API_TOKEN is not set."""
|
||||
resp = client.get("/api/feedback/status")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"enabled": False}
|
||||
|
||||
|
||||
def test_status_enabled_with_token(monkeypatch):
|
||||
"""Status is enabled when token is set and not in demo or cloud mode."""
|
||||
monkeypatch.delenv("CLOUD_MODE", raising=False)
|
||||
monkeypatch.delenv("DEMO_MODE", raising=False)
|
||||
monkeypatch.setenv("FORGEJO_API_TOKEN", "test-token")
|
||||
from dev_api import app
|
||||
c = TestClient(app)
|
||||
resp = c.get("/api/feedback/status")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"enabled": True}
|
||||
|
||||
|
||||
def test_status_disabled_in_demo_mode(monkeypatch):
|
||||
"""Status is disabled when DEMO_MODE=1 even if token is present."""
|
||||
monkeypatch.setenv("DEMO_MODE", "1")
|
||||
monkeypatch.setenv("FORGEJO_API_TOKEN", "test-token")
|
||||
monkeypatch.delenv("CLOUD_MODE", raising=False)
|
||||
from dev_api import app
|
||||
c = TestClient(app)
|
||||
resp = c.get("/api/feedback/status")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"enabled": False}
|
||||
|
||||
|
||||
def test_status_disabled_in_cloud_mode(monkeypatch):
|
||||
"""Status is disabled when CLOUD_MODE=1 (peregrine-specific rule).
|
||||
|
||||
_CLOUD_MODE is evaluated at import time, so we patch the module-level
|
||||
bool rather than the env var (the module is already cached in sys.modules).
|
||||
"""
|
||||
import dev_api as _dev_api_mod
|
||||
monkeypatch.setattr(_dev_api_mod, "_CLOUD_MODE", True)
|
||||
monkeypatch.setenv("FORGEJO_API_TOKEN", "test-token")
|
||||
monkeypatch.delenv("DEMO_MODE", raising=False)
|
||||
c = TestClient(_dev_api_mod.app)
|
||||
resp = c.get("/api/feedback/status")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json() == {"enabled": False}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# POST /api/feedback
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_FEEDBACK_PAYLOAD = {
|
||||
"title": "Test feedback",
|
||||
"description": "Something broke.",
|
||||
"type": "bug",
|
||||
"repro": "Click the button.",
|
||||
"tab": "Job Review",
|
||||
"submitter": "tester@example.com",
|
||||
}
|
||||
|
||||
|
||||
def test_post_feedback_503_when_no_token(client):
|
||||
"""POST returns 503 when FORGEJO_API_TOKEN is not configured."""
|
||||
resp = client.post("/api/feedback", json=_FEEDBACK_PAYLOAD)
|
||||
assert resp.status_code == 503
|
||||
assert "FORGEJO_API_TOKEN" in resp.json()["detail"]
|
||||
|
||||
|
||||
def test_post_feedback_403_in_demo_mode(monkeypatch):
|
||||
"""POST returns 403 when DEMO_MODE=1."""
|
||||
monkeypatch.setenv("DEMO_MODE", "1")
|
||||
monkeypatch.setenv("FORGEJO_API_TOKEN", "test-token")
|
||||
monkeypatch.delenv("CLOUD_MODE", raising=False)
|
||||
from dev_api import app
|
||||
c = TestClient(app)
|
||||
resp = c.post("/api/feedback", json=_FEEDBACK_PAYLOAD)
|
||||
assert resp.status_code == 403
|
||||
assert "demo" in resp.json()["detail"].lower()
|
||||
|
||||
|
||||
def test_post_feedback_200_creates_issue(monkeypatch):
|
||||
"""POST returns 200 with issue_number and issue_url when Forgejo calls succeed."""
|
||||
monkeypatch.setenv("FORGEJO_API_TOKEN", "test-token")
|
||||
monkeypatch.delenv("CLOUD_MODE", raising=False)
|
||||
monkeypatch.delenv("DEMO_MODE", raising=False)
|
||||
|
||||
mock_get_resp = MagicMock()
|
||||
mock_get_resp.ok = True
|
||||
mock_get_resp.json.return_value = [
|
||||
{"name": "beta-feedback", "id": 1},
|
||||
{"name": "needs-triage", "id": 2},
|
||||
{"name": "bug", "id": 3},
|
||||
]
|
||||
|
||||
mock_post_resp = MagicMock()
|
||||
mock_post_resp.ok = True
|
||||
mock_post_resp.json.return_value = {
|
||||
"number": 42,
|
||||
"html_url": "https://git.opensourcesolarpunk.com/Circuit-Forge/peregrine/issues/42",
|
||||
}
|
||||
|
||||
with patch("circuitforge_core.api.feedback.requests.get", return_value=mock_get_resp), \
|
||||
patch("circuitforge_core.api.feedback.requests.post", return_value=mock_post_resp):
|
||||
from dev_api import app
|
||||
c = TestClient(app)
|
||||
resp = c.post("/api/feedback", json=_FEEDBACK_PAYLOAD)
|
||||
|
||||
assert resp.status_code == 200
|
||||
body = resp.json()
|
||||
assert body["issue_number"] == 42
|
||||
assert "peregrine/issues/42" in body["issue_url"]
|
||||
Loading…
Reference in a new issue