Compare commits
1 Commits
fix/533
...
whip/579-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e4a8518c0 |
83
docs/GITEA_MENTION_HEARTBEAT.md
Normal file
83
docs/GITEA_MENTION_HEARTBEAT.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Gitea @mention Heartbeat for VPS Agents
|
||||
|
||||
Fixes [timmy-home#579](https://forge.alexanderwhitestone.com/Timmy_Foundation/timmy-home/issues/579):
|
||||
Ezra and Bezalel do not respond to Gitea @mention tagging.
|
||||
|
||||
## Problem
|
||||
|
||||
Two compounding root causes:
|
||||
1. Ezra/Bezalel not in `AGENT_USERS` — mentions detected but silently dropped
|
||||
2. Dispatch queue is Mac-local — VPS agents have no process reading it
|
||||
|
||||
## Solution
|
||||
|
||||
VPS-native heartbeat: each agent polls Gitea for unread notifications every 5 minutes,
|
||||
dispatches work locally via `hermes chat`, and posts results back as issue comments.
|
||||
|
||||
## Quick Deploy
|
||||
|
||||
```bash
|
||||
# On Mac:
|
||||
cd timmy-home/scripts
|
||||
|
||||
# Deploy to Ezra
|
||||
bash setup-vps-heartbeat.sh --agent ezra --host root@143.198.27.163
|
||||
|
||||
# Deploy to Bezalel
|
||||
bash setup-vps-heartbeat.sh --agent bezalel --host root@159.203.146.185
|
||||
```
|
||||
|
||||
## Manual Setup on VPS
|
||||
|
||||
```bash
|
||||
# 1. Copy script
|
||||
scp gitea-mention-heartbeat.sh root@VPS:/usr/local/bin/
|
||||
|
||||
# 2. Create token file
|
||||
ssh root@VPS 'mkdir -p ~/.config/gitea'
|
||||
ssh root@VPS 'echo "YOUR_GITEA_TOKEN" > ~/.config/gitea/ezra-token'
|
||||
ssh root@VPS 'chmod 600 ~/.config/gitea/ezra-token'
|
||||
|
||||
# 3. Add cron
|
||||
ssh root@VPS '(crontab -l 2>/dev/null; echo "*/5 * * * * /usr/local/bin/gitea-mention-heartbeat.sh --agent ezra --token-file ~/.config/gitea/ezra-token") | crontab -'
|
||||
|
||||
# 4. Test
|
||||
ssh root@VPS '/usr/local/bin/gitea-mention-heartbeat.sh --agent ezra --token-file ~/.config/gitea/ezra-token'
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────────────┐ ┌──────────────┐
|
||||
│ Gitea │────►│ Heartbeat (cron 5m) │────►│ hermes chat │
|
||||
│ @ezra │ │ Poll notifications │ │ (local) │
|
||||
│ mention │ │ Filter for agent │ │ │
|
||||
└──────────────┘ │ Dispatch via hermes │ └──────────────┘
|
||||
│ Post result back │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
1. Cron runs every 5 minutes
|
||||
2. Polls Gitea `/notifications` for unread items
|
||||
3. Filters for mentions of the agent name
|
||||
4. Dispatches to local `hermes chat` with issue context
|
||||
5. Posts the response as a Gitea comment
|
||||
6. Marks notification as read
|
||||
|
||||
## Monitoring
|
||||
|
||||
```bash
|
||||
# Watch logs
|
||||
ssh root@ezra 'tail -f ~/.hermes/logs/ezra-heartbeat.log'
|
||||
|
||||
# Check cron
|
||||
ssh root@ezra 'crontab -l | grep heartbeat'
|
||||
|
||||
# Test manually
|
||||
ssh root@ezra '/usr/local/bin/gitea-mention-heartbeat.sh --agent ezra --token-file ~/.config/gitea/ezra-token'
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- `gitea-mention-heartbeat.sh` — main heartbeat script (runs on VPS)
|
||||
- `setup-vps-heartbeat.sh` — deployment helper (runs on Mac)
|
||||
236
scripts/gitea-mention-heartbeat.sh
Executable file
236
scripts/gitea-mention-heartbeat.sh
Executable file
@@ -0,0 +1,236 @@
|
||||
#!/bin/bash
|
||||
# gitea-mention-heartbeat.sh — Generic Gitea @mention heartbeat for VPS agents
|
||||
#
|
||||
# Polls Gitea for unread notifications mentioning this agent, dispatches
|
||||
# work locally via hermes, and posts results back as issue comments.
|
||||
#
|
||||
# Usage:
|
||||
# gitea-mention-heartbeat.sh --agent ezra --token-file ~/.config/gitea/ezra-token
|
||||
# gitea-mention-heartbeat.sh --agent bezalel --token-file ~/.config/gitea/bezalel-token
|
||||
#
|
||||
# Install on VPS:
|
||||
# cp gitea-mention-heartbeat.sh /usr/local/bin/
|
||||
# chmod +x /usr/local/bin/gitea-mention-heartbeat.sh
|
||||
# # Add to crontab: */5 * * * * /usr/local/bin/gitea-mention-heartbeat.sh --agent ezra --token-file ~/.config/gitea/ezra-token
|
||||
#
|
||||
# Ref: timmy-home#579
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- Defaults ---
|
||||
BASE="${GITEA_API_BASE:-https://forge.alexanderwhitestone.com/api/v1}"
|
||||
LOG_DIR="${HOME}/.hermes/logs"
|
||||
MAX_DISPATCH=3
|
||||
DISPATCH_TIMEOUT=600 # 10 minutes
|
||||
|
||||
# --- Parse args ---
|
||||
AGENT=""
|
||||
TOKEN_FILE=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--agent) AGENT="$2"; shift 2 ;;
|
||||
--token-file) TOKEN_FILE="$2"; shift 2 ;;
|
||||
--base) BASE="$2"; shift 2 ;;
|
||||
--log-dir) LOG_DIR="$2"; shift 2 ;;
|
||||
--timeout) DISPATCH_TIMEOUT="$2"; shift 2 ;;
|
||||
*) echo "Unknown arg: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$AGENT" ] || [ -z "$TOKEN_FILE" ]; then
|
||||
echo "Usage: $0 --agent <name> --token-file <path>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$TOKEN_FILE" ]; then
|
||||
echo "Token file not found: $TOKEN_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOKEN=*** "$TOKEN_FILE" | tr -d '[:space:]')
|
||||
mkdir -p "$LOG_DIR"
|
||||
LOG="$LOG_DIR/${AGENT}-heartbeat.log"
|
||||
LOCKFILE="/tmp/${AGENT}-heartbeat.lock"
|
||||
PROCESSED_FILE="/tmp/${AGENT}-mentions-processed.txt"
|
||||
|
||||
touch "$PROCESSED_FILE"
|
||||
|
||||
# --- Helpers ---
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
|
||||
|
||||
post_comment() {
|
||||
local repo="$1" issue_num="$2" body="$3"
|
||||
local payload
|
||||
payload=$(python3 -c "import json,sys; print(json.dumps({'body': sys.argv[1]}))" "$body")
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
"$BASE/repos/$repo/issues/$issue_num/comments" > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
dispatch_local() {
|
||||
local prompt="$1"
|
||||
# Try hermes chat first, fall back to direct echo
|
||||
if command -v hermes &>/dev/null; then
|
||||
timeout "$DISPATCH_TIMEOUT" hermes chat -p "$prompt" 2>/dev/null || echo "Dispatch timed out after ${DISPATCH_TIMEOUT}s"
|
||||
else
|
||||
echo "hermes CLI not available — cannot dispatch locally"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Lock ---
|
||||
if [ -f "$LOCKFILE" ]; then
|
||||
lock_age=$(( $(date +%s) - $(stat -c %Y "$LOCKFILE" 2>/dev/null || stat -f %m "$LOCKFILE" 2>/dev/null || echo 0) ))
|
||||
if [ "$lock_age" -lt 300 ]; then
|
||||
log "SKIP: previous run still active (lock age: ${lock_age}s)"
|
||||
exit 0
|
||||
else
|
||||
log "WARN: stale lock (${lock_age}s), removing"
|
||||
rm -f "$LOCKFILE"
|
||||
fi
|
||||
fi
|
||||
trap 'rm -f "$LOCKFILE"' EXIT
|
||||
touch "$LOCKFILE"
|
||||
|
||||
# --- Poll notifications ---
|
||||
log "Polling mentions for @$AGENT..."
|
||||
|
||||
notifications=$(curl -sf \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
"$BASE/notifications?status-types=unread&limit=20" 2>/dev/null || echo "[]")
|
||||
|
||||
if [ "$notifications" = "[]" ] || [ -z "$notifications" ]; then
|
||||
log "No unread notifications"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse notifications
|
||||
dispatched=0
|
||||
|
||||
echo "$notifications" | python3 -c "
|
||||
import json, sys
|
||||
try:
|
||||
notifs = json.loads(sys.stdin.buffer.read())
|
||||
except:
|
||||
sys.exit(0)
|
||||
for n in notifs:
|
||||
subject = n.get('subject', {})
|
||||
repo = n.get('repository', {}).get('full_name', '')
|
||||
title = subject.get('title', '')
|
||||
url = subject.get('latest_comment_url', '') or subject.get('url', '')
|
||||
nid = n.get('id', '')
|
||||
# Only process if the subject title or URL mentions our agent
|
||||
title_lower = title.lower()
|
||||
if '${AGENT}' in title_lower or '@${AGENT}' in title_lower:
|
||||
print(f'{nid}|{repo}|{title}|{url}')
|
||||
else:
|
||||
# Check comment body for mentions
|
||||
print(f'SKIP|{nid}|{repo}|{title}|{url}')
|
||||
" 2>/dev/null | while IFS='|' read -r flag_or_nid rest; do
|
||||
|
||||
if [ "$flag_or_nid" = "SKIP" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
nid="$flag_or_nid"
|
||||
repo=$(echo "$rest" | cut -d'|' -f1)
|
||||
title=$(echo "$rest" | cut -d'|' -f2)
|
||||
url=$(echo "$rest" | cut -d'|' -f3)
|
||||
|
||||
[ -z "$nid" ] && continue
|
||||
|
||||
# Skip if already processed
|
||||
grep -q "^${nid}$" "$PROCESSED_FILE" && continue
|
||||
|
||||
# Extract issue number
|
||||
issue_num=$(echo "$url" | grep -o '/issues/[0-9]*' | grep -o '[0-9]*' || echo "")
|
||||
|
||||
if [ -z "$issue_num" ]; then
|
||||
log "SKIP: could not extract issue number from $url"
|
||||
echo "$nid" >> "$PROCESSED_FILE"
|
||||
continue
|
||||
fi
|
||||
|
||||
log "FOUND: $repo #$issue_num — $title"
|
||||
|
||||
# Get comment body
|
||||
comment_body=""
|
||||
if [ -n "$url" ]; then
|
||||
comment_body=$(curl -sf -H "Authorization: token $TOKEN" "$url" 2>/dev/null | \
|
||||
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('body','')[:2000])" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
# Post acknowledgment
|
||||
post_comment "$repo" "$issue_num" "🔵 **@$AGENT picking up this task** via VPS heartbeat.
|
||||
|
||||
Dispatched: $(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
Agent: $AGENT
|
||||
Host: $(hostname)"
|
||||
|
||||
# Build dispatch prompt
|
||||
prompt="You are @$AGENT, an AI agent on the Timmy Foundation team.
|
||||
|
||||
You were mentioned in Gitea issue #$issue_num in repo $repo.
|
||||
|
||||
ISSUE: $title
|
||||
|
||||
COMMENT THAT MENTIONED YOU:
|
||||
$comment_body
|
||||
|
||||
YOUR TASK:
|
||||
1. Read the mention carefully
|
||||
2. If asked a question, answer it directly
|
||||
3. If asked to do work, do it (create a PR if code changes are needed)
|
||||
4. Post your response as a comment on the issue using the Gitea API
|
||||
5. Be concise and actionable
|
||||
|
||||
When done, summarize what you did."
|
||||
|
||||
# Dispatch locally
|
||||
log "DISPATCH: $repo #$issue_num to local hermes (timeout: ${DISPATCH_TIMEOUT}s)"
|
||||
result=$(dispatch_local "$prompt" 2>&1 || echo "Dispatch failed")
|
||||
|
||||
# Post result
|
||||
if [ -n "$result" ] && [ "$result" != "Dispatch failed" ]; then
|
||||
escaped=$(echo "$result" | head -50 | python3 -c "import sys,json; print(json.dumps(sys.stdin.read())[1:-1])" 2>/dev/null || echo "See agent logs")
|
||||
post_comment "$repo" "$issue_num" "🟢 **@$AGENT response:**
|
||||
|
||||
$escaped
|
||||
|
||||
---
|
||||
Completed: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
else
|
||||
post_comment "$repo" "$issue_num" "🔴 **@$AGENT dispatch failed or timed out.**
|
||||
|
||||
Timeout: ${DISPATCH_TIMEOUT}s
|
||||
Timestamp: $(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
The task may need manual attention."
|
||||
fi
|
||||
|
||||
# Mark notification as read
|
||||
curl -sf -X PATCH \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
"$BASE/notifications/threads/$nid" > /dev/null 2>&1 || true
|
||||
|
||||
# Mark as processed
|
||||
echo "$nid" >> "$PROCESSED_FILE"
|
||||
|
||||
dispatched=$((dispatched + 1))
|
||||
log "DISPATCHED: $repo #$issue_num ($dispatched/$MAX_DISPATCH)"
|
||||
|
||||
if [ "$dispatched" -ge "$MAX_DISPATCH" ]; then
|
||||
log "CAPPED: reached $MAX_DISPATCH dispatches"
|
||||
break
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$dispatched" -eq 0 ]; then
|
||||
log "No new mentions for @$AGENT"
|
||||
else
|
||||
log "Completed $dispatched dispatch(es) for @$AGENT"
|
||||
fi
|
||||
87
scripts/setup-vps-heartbeat.sh
Executable file
87
scripts/setup-vps-heartbeat.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/bin/bash
|
||||
# setup-vps-heartbeat.sh — Deploy Gitea mention heartbeat on a VPS agent box
|
||||
#
|
||||
# Usage:
|
||||
# setup-vps-heartbeat.sh --agent ezra --host root@143.198.27.163
|
||||
# setup-vps-heartbeat.sh --agent bezalel --host root@159.203.146.185
|
||||
#
|
||||
# What it does:
|
||||
# 1. Copies gitea-mention-heartbeat.sh to the VPS
|
||||
# 2. Creates a Gitea token file (prompts if missing)
|
||||
# 3. Installs a cron job (every 5 minutes)
|
||||
# 4. Tests connectivity
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
HEARTBEAT_SCRIPT="$SCRIPT_DIR/gitea-mention-heartbeat.sh"
|
||||
|
||||
AGENT=""
|
||||
HOST=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--agent) AGENT="$2"; shift 2 ;;
|
||||
--host) HOST="$2"; shift 2 ;;
|
||||
*) echo "Unknown arg: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$AGENT" ] || [ -z "$HOST" ]; then
|
||||
echo "Usage: $0 --agent <name> --host <user@host>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Deploying Gitea mention heartbeat for @$AGENT on $HOST ==="
|
||||
|
||||
# 1. Copy heartbeat script
|
||||
echo "Copying heartbeat script..."
|
||||
scp -q "$HEARTBEAT_SCRIPT" "$HOST:/usr/local/bin/gitea-mention-heartbeat.sh"
|
||||
ssh "$HOST" "chmod +x /usr/local/bin/gitea-mention-heartbeat.sh"
|
||||
|
||||
# 2. Ensure token file exists
|
||||
echo "Checking Gitea token..."
|
||||
TOKEN_EXISTS=$(ssh "$HOST" "test -f ~/.config/gitea/${AGENT}-token && echo yes || echo no")
|
||||
if [ "$TOKEN_EXISTS" = "no" ]; then
|
||||
echo ""
|
||||
echo "⚠️ No token file found at ~/.config/gitea/${AGENT}-token on $HOST"
|
||||
echo " Create a Gitea API token for @$AGENT and save it:"
|
||||
echo " ssh $HOST 'mkdir -p ~/.config/gitea && echo YOUR_TOKEN > ~/.config/gitea/${AGENT}-token && chmod 600 ~/.config/gitea/${AGENT}-token'"
|
||||
echo ""
|
||||
echo " Token needs: issue (read/write), notification (read) permissions"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# 3. Install cron job
|
||||
echo "Installing cron job (every 5 minutes)..."
|
||||
CRON_LINE="*/5 * * * * /usr/local/bin/gitea-mention-heartbeat.sh --agent $AGENT --token-file ~/.config/gitea/${AGENT}-token >> ~/.hermes/logs/${AGENT}-heartbeat-cron.log 2>&1"
|
||||
|
||||
# Check if cron already has this line
|
||||
EXISTING=$(ssh "$HOST" "crontab -l 2>/dev/null | grep -c '${AGENT}-heartbeat' || echo 0")
|
||||
if [ "$EXISTING" -gt 0 ]; then
|
||||
echo "Cron job already exists, updating..."
|
||||
ssh "$HOST" "crontab -l 2>/dev/null | grep -v '${AGENT}-heartbeat' | { cat; echo '$CRON_LINE'; } | crontab -"
|
||||
else
|
||||
echo "Adding cron job..."
|
||||
ssh "$HOST" "(crontab -l 2>/dev/null || true; echo '$CRON_LINE') | crontab -"
|
||||
fi
|
||||
|
||||
# 4. Ensure log directory
|
||||
ssh "$HOST" "mkdir -p ~/.hermes/logs"
|
||||
|
||||
# 5. Test connectivity
|
||||
echo ""
|
||||
echo "Testing Gitea API connectivity..."
|
||||
TEST_RESULT=$(ssh "$HOST" "curl -sf -H \"Authorization: token \$(cat ~/.config/gitea/${AGENT}-token 2>/dev/null || echo 'MISSING')\" 'https://forge.alexanderwhitestone.com/api/v1/user' 2>/dev/null | python3 -c \"import json,sys; d=json.load(sys.stdin); print(d.get('login','ERROR'))\" 2>/dev/null || echo 'FAILED'")
|
||||
echo " Token belongs to: $TEST_RESULT"
|
||||
|
||||
# 6. Dry run
|
||||
echo ""
|
||||
echo "Running dry test..."
|
||||
ssh "$HOST" "/usr/local/bin/gitea-mention-heartbeat.sh --agent $AGENT --token-file ~/.config/gitea/${AGENT}-token" 2>&1 | tail -5
|
||||
|
||||
echo ""
|
||||
echo "=== Deployment complete for @$AGENT on $HOST ==="
|
||||
echo ""
|
||||
echo "Logs: ssh $HOST 'tail -f ~/.hermes/logs/${AGENT}-heartbeat.log'"
|
||||
echo "Cron: ssh $HOST 'crontab -l | grep ${AGENT}'"
|
||||
0
tests/scripts/__init__.py
Normal file
0
tests/scripts/__init__.py
Normal file
107
tests/scripts/test_gitea_heartbeat.py
Normal file
107
tests/scripts/test_gitea_heartbeat.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""Tests for Gitea @mention heartbeat scripts."""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def heartbeat_script():
|
||||
return Path(__file__).parent.parent.parent / "scripts" / "gitea-mention-heartbeat.sh"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_token(tmp_path):
|
||||
token_file = tmp_path / "test-token"
|
||||
token_file.write_text("fake-token-12345")
|
||||
return token_file
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_log_dir(tmp_path):
|
||||
log_dir = tmp_path / "logs"
|
||||
log_dir.mkdir()
|
||||
return log_dir
|
||||
|
||||
|
||||
class TestArgumentParsing:
|
||||
def test_requires_agent(self, heartbeat_script):
|
||||
result = subprocess.run(
|
||||
["bash", str(heartbeat_script), "--token-file", "/tmp/fake"],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_requires_token_file(self, heartbeat_script):
|
||||
result = subprocess.run(
|
||||
["bash", str(heartbeat_script), "--agent", "test"],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
assert result.returncode != 0
|
||||
|
||||
def test_token_file_must_exist(self, heartbeat_script):
|
||||
result = subprocess.run(
|
||||
["bash", str(heartbeat_script), "--agent", "test", "--token-file", "/nonexistent/token"],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
assert result.returncode != 0
|
||||
|
||||
|
||||
class TestScriptStructure:
|
||||
def test_has_required_functions(self, heartbeat_script):
|
||||
content = heartbeat_script.read_text()
|
||||
assert "post_comment" in content
|
||||
assert "dispatch_local" in content
|
||||
assert "log()" in content
|
||||
|
||||
def test_has_lock_mechanism(self, heartbeat_script):
|
||||
content = heartbeat_script.read_text()
|
||||
assert "LOCKFILE" in content
|
||||
assert "trap" in content
|
||||
|
||||
def test_has_notification_polling(self, heartbeat_script):
|
||||
content = heartbeat_script.read_text()
|
||||
assert "/notifications" in content
|
||||
assert "Authorization: token" in content
|
||||
|
||||
def test_has_dispatch_cap(self, heartbeat_script):
|
||||
content = heartbeat_script.read_text()
|
||||
assert "MAX_DISPATCH" in content
|
||||
|
||||
def test_has_processed_tracking(self, heartbeat_script):
|
||||
content = heartbeat_script.read_text()
|
||||
assert "PROCESSED_FILE" in content
|
||||
|
||||
|
||||
class TestSetupScript:
|
||||
def test_setup_exists(self):
|
||||
setup = Path(__file__).parent.parent.parent / "scripts" / "setup-vps-heartbeat.sh"
|
||||
assert setup.exists()
|
||||
assert "setup-vps-heartbeat.sh" in setup.name
|
||||
|
||||
def test_setup_has_required_args(self):
|
||||
setup = Path(__file__).parent.parent.parent / "scripts" / "setup-vps-heartbeat.sh"
|
||||
content = setup.read_text()
|
||||
assert "--agent" in content
|
||||
assert "--host" in content
|
||||
|
||||
def test_setup_installs_cron(self):
|
||||
setup = Path(__file__).parent.parent.parent / "scripts" / "setup-vps-heartbeat.sh"
|
||||
content = setup.read_text()
|
||||
assert "crontab" in content
|
||||
assert "*/5" in content
|
||||
|
||||
|
||||
class TestDocumentation:
|
||||
def test_docs_exist(self):
|
||||
docs = Path(__file__).parent.parent.parent / "docs" / "GITEA_MENTION_HEARTBEAT.md"
|
||||
assert docs.exists()
|
||||
|
||||
def test_docs_reference_issue(self):
|
||||
docs = Path(__file__).parent.parent.parent / "docs" / "GITEA_MENTION_HEARTBEAT.md"
|
||||
content = docs.read_text()
|
||||
assert "579" in content
|
||||
Reference in New Issue
Block a user