chore: remove outdated docs, update cleanup script and release workflow
This commit is contained in:
parent
875c64ecbc
commit
30d2d4ecd3
3 changed files with 193 additions and 430 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# pulse-sensor-cleanup.sh - Removes Pulse SSH keys from Proxmox nodes when they're removed from Pulse
|
# pulse-sensor-cleanup.sh - Complete Pulse footprint removal when nodes are removed
|
||||||
|
# Removes: SSH keys, proxy service, binaries, API tokens, and LXC bind mounts
|
||||||
# This script is triggered by systemd path unit when cleanup-request.json is created
|
# This script is triggered by systemd path unit when cleanup-request.json is created
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
@ -8,7 +9,9 @@ set -euo pipefail
|
||||||
# Configuration
|
# Configuration
|
||||||
WORK_DIR="/var/lib/pulse-sensor-proxy"
|
WORK_DIR="/var/lib/pulse-sensor-proxy"
|
||||||
CLEANUP_REQUEST="${WORK_DIR}/cleanup-request.json"
|
CLEANUP_REQUEST="${WORK_DIR}/cleanup-request.json"
|
||||||
|
LOCKFILE="${WORK_DIR}/cleanup.lock"
|
||||||
LOG_TAG="pulse-sensor-cleanup"
|
LOG_TAG="pulse-sensor-cleanup"
|
||||||
|
INSTALLER_PATH="/opt/pulse/sensor-proxy/install-sensor-proxy.sh"
|
||||||
|
|
||||||
# Logging functions
|
# Logging functions
|
||||||
log_info() {
|
log_info() {
|
||||||
|
|
@ -26,6 +29,13 @@ log_error() {
|
||||||
echo "[ERROR] $1" >&2
|
echo "[ERROR] $1" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Acquire exclusive lock to prevent concurrent cleanup runs
|
||||||
|
exec 200>"$LOCKFILE"
|
||||||
|
if ! flock -n 200; then
|
||||||
|
log_info "Another cleanup instance is running, exiting"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if cleanup request file exists
|
# Check if cleanup request file exists
|
||||||
if [[ ! -f "$CLEANUP_REQUEST" ]]; then
|
if [[ ! -f "$CLEANUP_REQUEST" ]]; then
|
||||||
log_info "No cleanup request found at $CLEANUP_REQUEST"
|
log_info "No cleanup request found at $CLEANUP_REQUEST"
|
||||||
|
|
@ -41,8 +51,12 @@ REQUESTED_AT=$(echo "$CLEANUP_DATA" | grep -o '"requestedAt":"[^"]*"' | cut -d'"
|
||||||
|
|
||||||
log_info "Cleanup requested at: ${REQUESTED_AT:-unknown}"
|
log_info "Cleanup requested at: ${REQUESTED_AT:-unknown}"
|
||||||
|
|
||||||
# Remove the cleanup request file immediately to prevent re-processing
|
# Rename request file to .processing to prevent re-triggering while allowing retry on failure
|
||||||
rm -f "$CLEANUP_REQUEST"
|
PROCESSING_FILE="${CLEANUP_REQUEST}.processing"
|
||||||
|
mv "$CLEANUP_REQUEST" "$PROCESSING_FILE" 2>/dev/null || {
|
||||||
|
log_warn "Failed to rename cleanup request file, may have been processed by another instance"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
# If no specific host was provided, clean up all known nodes
|
# If no specific host was provided, clean up all known nodes
|
||||||
if [[ -z "$HOST" ]]; then
|
if [[ -z "$HOST" ]]; then
|
||||||
|
|
@ -50,7 +64,7 @@ if [[ -z "$HOST" ]]; then
|
||||||
|
|
||||||
# Discover cluster nodes
|
# Discover cluster nodes
|
||||||
if command -v pvecm >/dev/null 2>&1; then
|
if command -v pvecm >/dev/null 2>&1; then
|
||||||
CLUSTER_NODES=$(pvecm status 2>/dev/null | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print $i}')
|
CLUSTER_NODES=$(pvecm status 2>/dev/null | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {for(i=1;i<=NF;i++) if($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) print $i}' || true)
|
||||||
|
|
||||||
if [[ -n "$CLUSTER_NODES" ]]; then
|
if [[ -n "$CLUSTER_NODES" ]]; then
|
||||||
for node_ip in $CLUSTER_NODES; do
|
for node_ip in $CLUSTER_NODES; do
|
||||||
|
|
@ -79,13 +93,16 @@ if [[ -z "$HOST" ]]; then
|
||||||
else
|
else
|
||||||
log_info "Cleaning up specific host: $HOST"
|
log_info "Cleaning up specific host: $HOST"
|
||||||
|
|
||||||
# Extract IP from host URL
|
# Extract hostname/IP from host URL
|
||||||
HOST_CLEAN=$(echo "$HOST" | sed -e 's|^https\?://||' -e 's|:.*$||')
|
HOST_CLEAN=$(echo "$HOST" | sed -e 's|^https\?://||' -e 's|:.*$||')
|
||||||
|
|
||||||
# Check if this is localhost
|
# Check if this is localhost (by IP, hostname, or FQDN)
|
||||||
LOCAL_IPS=$(hostname -I 2>/dev/null || echo "")
|
LOCAL_IPS=$(hostname -I 2>/dev/null || echo "")
|
||||||
|
LOCAL_HOSTNAME=$(hostname 2>/dev/null || echo "")
|
||||||
|
LOCAL_FQDN=$(hostname -f 2>/dev/null || echo "")
|
||||||
IS_LOCAL=false
|
IS_LOCAL=false
|
||||||
|
|
||||||
|
# Check against all local IPs
|
||||||
for local_ip in $LOCAL_IPS; do
|
for local_ip in $LOCAL_IPS; do
|
||||||
if [[ "$HOST_CLEAN" == "$local_ip" ]]; then
|
if [[ "$HOST_CLEAN" == "$local_ip" ]]; then
|
||||||
IS_LOCAL=true
|
IS_LOCAL=true
|
||||||
|
|
@ -93,15 +110,143 @@ else
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "$HOST_CLEAN" == "127.0.0.1" || "$HOST_CLEAN" == "localhost" ]]; then
|
# Check against hostname and FQDN
|
||||||
|
if [[ "$HOST_CLEAN" == "127.0.0.1" || "$HOST_CLEAN" == "localhost" || \
|
||||||
|
"$HOST_CLEAN" == "$LOCAL_HOSTNAME" || "$HOST_CLEAN" == "$LOCAL_FQDN" ]]; then
|
||||||
IS_LOCAL=true
|
IS_LOCAL=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$IS_LOCAL" == true ]]; then
|
if [[ "$IS_LOCAL" == true ]]; then
|
||||||
log_info "Cleaning up localhost SSH keys"
|
log_info "Performing full cleanup on localhost"
|
||||||
|
|
||||||
|
# 1. Remove SSH keys
|
||||||
|
log_info "Removing SSH keys from authorized_keys"
|
||||||
sed -i -e '/# pulse-managed-key$/d' -e '/# pulse-proxy-key$/d' /root/.ssh/authorized_keys 2>&1 | \
|
sed -i -e '/# pulse-managed-key$/d' -e '/# pulse-proxy-key$/d' /root/.ssh/authorized_keys 2>&1 | \
|
||||||
logger -t "$LOG_TAG" -p user.info || \
|
logger -t "$LOG_TAG" -p user.info || \
|
||||||
log_warn "Failed to clean up SSH keys on localhost"
|
log_warn "Failed to clean up SSH keys"
|
||||||
|
|
||||||
|
# 2. Delete API tokens and user
|
||||||
|
log_info "Removing Proxmox API tokens and pulse-monitor user"
|
||||||
|
if command -v pveum >/dev/null 2>&1; then
|
||||||
|
# Try JSON output first (pveum with --output-format json)
|
||||||
|
TOKEN_IDS=""
|
||||||
|
if command -v python3 >/dev/null 2>&1; then
|
||||||
|
# Try pveum with JSON output
|
||||||
|
if TOKEN_JSON=$(pveum user token list pulse-monitor@pam --output-format json 2>/dev/null); then
|
||||||
|
TOKEN_IDS=$(echo "$TOKEN_JSON" | python3 -c '
|
||||||
|
import sys, json
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
if isinstance(data, list):
|
||||||
|
for item in data:
|
||||||
|
if "tokenid" in item:
|
||||||
|
print(item["tokenid"])
|
||||||
|
except: pass
|
||||||
|
' || true)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fall back to pvesh JSON API if pveum JSON didn't work
|
||||||
|
if [[ -z "$TOKEN_IDS" ]] && command -v pvesh >/dev/null 2>&1; then
|
||||||
|
if TOKEN_JSON=$(pvesh get /access/users/pulse-monitor@pam/token 2>/dev/null); then
|
||||||
|
TOKEN_IDS=$(echo "$TOKEN_JSON" | python3 -c '
|
||||||
|
import sys, json
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
if isinstance(data, dict) and "data" in data:
|
||||||
|
for item in data["data"]:
|
||||||
|
if "tokenid" in item:
|
||||||
|
print(item["tokenid"])
|
||||||
|
except: pass
|
||||||
|
' 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Last resort: parse table output with better filtering
|
||||||
|
if [[ -z "$TOKEN_IDS" ]]; then
|
||||||
|
TOKEN_IDS=$(pveum user token list pulse-monitor@pam 2>/dev/null | \
|
||||||
|
awk 'NR>1 && /^[[:space:]]*pulse/ {print $1}' | grep -v '^[│┌└╞─]' | grep -v '^$' || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$TOKEN_IDS" ]]; then
|
||||||
|
for token_id in $TOKEN_IDS; do
|
||||||
|
log_info "Deleting API token: $token_id"
|
||||||
|
pveum user token remove pulse-monitor@pam "${token_id}" 2>&1 | \
|
||||||
|
logger -t "$LOG_TAG" -p user.info || \
|
||||||
|
log_warn "Failed to delete token $token_id"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
log_info "No API tokens found for pulse-monitor@pam"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove the pulse-monitor user
|
||||||
|
log_info "Removing pulse-monitor@pam user"
|
||||||
|
pveum user delete pulse-monitor@pam 2>&1 | \
|
||||||
|
logger -t "$LOG_TAG" -p user.info || \
|
||||||
|
log_warn "pulse-monitor@pam user not found or already removed"
|
||||||
|
else
|
||||||
|
log_warn "pveum command not available, skipping API token cleanup"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Remove LXC bind mounts
|
||||||
|
log_info "Removing LXC bind mounts from container configs"
|
||||||
|
if command -v pct >/dev/null 2>&1; then
|
||||||
|
for ctid in $(pct list 2>/dev/null | awk 'NR>1 {print $1}' || true); do
|
||||||
|
CONF_FILE="/etc/pve/lxc/${ctid}.conf"
|
||||||
|
if [[ -f "$CONF_FILE" ]]; then
|
||||||
|
# Find pulse-sensor-proxy mount points and remove them using pct
|
||||||
|
for mp_key in $(grep -o "^mp[0-9]\+:" "$CONF_FILE" | grep -f <(grep "pulse-sensor-proxy" "$CONF_FILE" | grep -o "^mp[0-9]\+:") || true); do
|
||||||
|
mp_num="${mp_key%:}"
|
||||||
|
log_info "Removing ${mp_num} (pulse-sensor-proxy) from container $ctid"
|
||||||
|
if pct set "$ctid" -delete "${mp_num}" 2>&1 | logger -t "$LOG_TAG" -p user.info; then
|
||||||
|
log_info "Successfully removed ${mp_num} from container $ctid"
|
||||||
|
else
|
||||||
|
log_warn "Failed to remove ${mp_num} from container $ctid"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Uninstall proxy service and remove binaries via isolated transient unit
|
||||||
|
log_info "Starting full uninstallation (service, binaries, configs)"
|
||||||
|
if [[ -x "$INSTALLER_PATH" ]]; then
|
||||||
|
# Use systemd-run to create isolated transient unit that won't be killed
|
||||||
|
# when we stop pulse-sensor-proxy.service
|
||||||
|
if command -v systemd-run >/dev/null 2>&1; then
|
||||||
|
# Use UUID for unique unit name (prevents same-second collisions)
|
||||||
|
UNINSTALL_UUID=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || date +%s%N)
|
||||||
|
UNINSTALL_UNIT="pulse-uninstall-${UNINSTALL_UUID}"
|
||||||
|
log_info "Spawning isolated uninstaller unit: $UNINSTALL_UNIT"
|
||||||
|
|
||||||
|
systemd-run \
|
||||||
|
--unit="${UNINSTALL_UNIT}" \
|
||||||
|
--property="Type=oneshot" \
|
||||||
|
--property="Conflicts=pulse-sensor-proxy.service" \
|
||||||
|
--collect \
|
||||||
|
--wait \
|
||||||
|
--quiet \
|
||||||
|
-- bash -c "$INSTALLER_PATH --uninstall --purge --quiet >> /var/log/pulse/sensor-proxy/uninstall.log 2>&1" \
|
||||||
|
2>&1 | logger -t "$LOG_TAG" -p user.info
|
||||||
|
|
||||||
|
UNINSTALL_EXIT=$?
|
||||||
|
if [[ $UNINSTALL_EXIT -eq 0 ]]; then
|
||||||
|
log_info "Uninstaller completed successfully"
|
||||||
|
else
|
||||||
|
log_error "Uninstaller failed with exit code $UNINSTALL_EXIT"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warn "systemd-run not available, attempting direct uninstall (may fail)"
|
||||||
|
bash "$INSTALLER_PATH" --uninstall --quiet >> /var/log/pulse/sensor-proxy/uninstall.log 2>&1 || \
|
||||||
|
log_error "Uninstaller failed - manual cleanup may be required"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warn "Installer not found at $INSTALLER_PATH, cannot run uninstaller"
|
||||||
|
log_info "Manual cleanup required: systemctl stop pulse-sensor-proxy && systemctl disable pulse-sensor-proxy"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Localhost cleanup initiated (uninstaller running in background)"
|
||||||
else
|
else
|
||||||
log_info "Cleaning up remote host: $HOST_CLEAN"
|
log_info "Cleaning up remote host: $HOST_CLEAN"
|
||||||
|
|
||||||
|
|
@ -134,56 +279,8 @@ else
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Full cleanup: uninstall proxy service, remove bind mounts, delete API tokens
|
# Remove processing file on success
|
||||||
log_info "Starting full cleanup: uninstalling proxy service and removing remaining artifacts"
|
rm -f "$PROCESSING_FILE"
|
||||||
|
|
||||||
# 1. Run the proxy uninstaller if available
|
log_info "Cleanup completed successfully"
|
||||||
INSTALLER_PATH="/usr/local/share/pulse/install-sensor-proxy.sh"
|
|
||||||
if [[ -x "$INSTALLER_PATH" ]]; then
|
|
||||||
log_info "Running proxy uninstaller to remove service and bind mounts"
|
|
||||||
if "$INSTALLER_PATH" --uninstall --quiet; then
|
|
||||||
log_info "Proxy service uninstalled successfully"
|
|
||||||
else
|
|
||||||
log_warn "Proxy uninstaller reported errors (may already be removed)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log_warn "Proxy uninstaller not found at $INSTALLER_PATH - manual cleanup may be required"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2. Delete Proxmox API tokens
|
|
||||||
log_info "Removing Proxmox API tokens for pulse-monitor user"
|
|
||||||
|
|
||||||
# Find all API tokens for pulse-monitor user
|
|
||||||
if command -v pveum >/dev/null 2>&1; then
|
|
||||||
# List tokens for pulse-monitor user
|
|
||||||
TOKENS=$(pveum user token list pulse-monitor@pam 2>/dev/null | awk 'NR>1 {print $1}' || echo "")
|
|
||||||
|
|
||||||
if [[ -n "$TOKENS" ]]; then
|
|
||||||
for token_id in $TOKENS; do
|
|
||||||
log_info "Removing API token: pulse-monitor@pam!${token_id}"
|
|
||||||
if pveum user token remove pulse-monitor@pam "${token_id}" 2>/dev/null; then
|
|
||||||
log_info "Successfully removed token: ${token_id}"
|
|
||||||
else
|
|
||||||
log_warn "Failed to remove token: ${token_id}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
log_info "No API tokens found for pulse-monitor@pam user"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove the pulse-monitor user entirely
|
|
||||||
if pveum user list 2>/dev/null | grep -q "pulse-monitor@pam"; then
|
|
||||||
log_info "Removing pulse-monitor@pam user"
|
|
||||||
if pveum user delete pulse-monitor@pam 2>/dev/null; then
|
|
||||||
log_info "Successfully removed pulse-monitor@pam user"
|
|
||||||
else
|
|
||||||
log_warn "Failed to remove pulse-monitor@pam user"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log_warn "pveum command not available - cannot remove API tokens automatically"
|
|
||||||
log_info "Manual cleanup: pveum user delete pulse-monitor@pam"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "Full cleanup completed successfully"
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
||||||
|
|
@ -100,18 +100,46 @@ if [ -f "$NOTES_FILE" ]; then
|
||||||
echo "Release notes file ignored"
|
echo "Release notes file ignored"
|
||||||
NOTES_FILE=""
|
NOTES_FILE=""
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
else
|
else
|
||||||
echo "No release notes file found at ${NOTES_FILE}"
|
echo "No release notes file found at ${NOTES_FILE}"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Create release notes manually or let the workflow prompt you."
|
read -p "Generate release notes automatically? [Y/n] " -n 1 -r
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Continue without release notes file? [y/N] " -n 1 -r
|
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
||||||
echo ""
|
echo "Generating release notes..."
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
# Try to find previous tag for better context
|
||||||
echo "Aborted"
|
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||||
exit 1
|
|
||||||
|
if ./scripts/generate-release-notes.sh "$VERSION" "$PREV_TAG" > "$NOTES_FILE"; then
|
||||||
|
echo "Release notes generated at ${NOTES_FILE}"
|
||||||
|
echo ""
|
||||||
|
# Show first few lines
|
||||||
|
head -n 20 "$NOTES_FILE"
|
||||||
|
echo "... (truncated)"
|
||||||
|
echo ""
|
||||||
|
read -p "Use these release notes? [Y/n] " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
|
echo "Release notes rejected."
|
||||||
|
rm "$NOTES_FILE"
|
||||||
|
NOTES_FILE=""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Failed to generate release notes."
|
||||||
|
NOTES_FILE=""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
NOTES_FILE=""
|
||||||
fi
|
fi
|
||||||
NOTES_FILE=""
|
fi
|
||||||
|
|
||||||
|
if [ -z "$NOTES_FILE" ]; then
|
||||||
|
echo "❌ Error: Release notes are required"
|
||||||
|
echo ""
|
||||||
|
echo "Create ${NOTES_FILE} manually, then run this script again."
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Trigger the workflow
|
# Trigger the workflow
|
||||||
|
|
@ -122,11 +150,8 @@ if [ -n "$NOTES_FILE" ]; then
|
||||||
-f version="${VERSION}" \
|
-f version="${VERSION}" \
|
||||||
-f release_notes="$(cat "$NOTES_FILE")"
|
-f release_notes="$(cat "$NOTES_FILE")"
|
||||||
else
|
else
|
||||||
echo ""
|
# This should be unreachable due to check above, but kept for safety
|
||||||
echo "❌ Error: Release notes are required"
|
echo "❌ Error: Release notes are required"
|
||||||
echo ""
|
|
||||||
echo "Create ${NOTES_FILE} with your release notes, then run this script again."
|
|
||||||
echo ""
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,359 +0,0 @@
|
||||||
# Update Integration Tests - Implementation Summary
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This implementation provides a comprehensive end-to-end testing framework for the Pulse update flow, validating the entire path from UI to backend with controllable test scenarios.
|
|
||||||
|
|
||||||
## What Was Built
|
|
||||||
|
|
||||||
### 1. Test Harness Infrastructure
|
|
||||||
|
|
||||||
#### Mock GitHub Release Server (`mock-github-server/`)
|
|
||||||
- **Language**: Go
|
|
||||||
- **Features**:
|
|
||||||
- Simulates GitHub Releases API
|
|
||||||
- Generates realistic release tarballs with checksums
|
|
||||||
- Controllable failure modes via environment variables
|
|
||||||
- Rate limiting simulation
|
|
||||||
- Stale release detection
|
|
||||||
- Network error simulation
|
|
||||||
|
|
||||||
#### Docker Compose Test Environment (`docker-compose.test.yml`)
|
|
||||||
- **Services**:
|
|
||||||
- `pulse-test`: Pulse server configured for testing
|
|
||||||
- `mock-github`: Mock GitHub API server
|
|
||||||
- **Features**:
|
|
||||||
- Isolated network for testing
|
|
||||||
- Health checks for both services
|
|
||||||
- Environment-based configuration for different test scenarios
|
|
||||||
- Automatic cleanup after tests
|
|
||||||
|
|
||||||
### 2. Playwright Test Suite
|
|
||||||
|
|
||||||
#### Test Infrastructure
|
|
||||||
- **Framework**: Playwright with TypeScript
|
|
||||||
- **Configuration**: `playwright.config.ts`
|
|
||||||
- **Helpers**: `tests/helpers.ts` with reusable test utilities
|
|
||||||
- **Browser**: Chromium (headless in CI)
|
|
||||||
|
|
||||||
#### Test Scenarios Implemented
|
|
||||||
|
|
||||||
##### 01. Happy Path (`01-happy-path.spec.ts`)
|
|
||||||
- ✅ Display update banner when update is available
|
|
||||||
- ✅ Show confirmation modal with version details
|
|
||||||
- ✅ Show progress modal during update
|
|
||||||
- ✅ Progress modal appears exactly once (no duplicates)
|
|
||||||
- ✅ Display different stages (downloading, verifying, extracting, etc.)
|
|
||||||
- ✅ Verify checksum during update
|
|
||||||
- ✅ Complete end-to-end update flow
|
|
||||||
- ✅ Include release notes in update banner
|
|
||||||
|
|
||||||
**Tests**: 8 test cases
|
|
||||||
|
|
||||||
##### 02. Bad Checksums (`02-bad-checksums.spec.ts`)
|
|
||||||
- ✅ Display error when checksum validation fails
|
|
||||||
- ✅ Show error modal EXACTLY ONCE (not twice) ⭐ **Critical for v4.28.0 issue**
|
|
||||||
- ✅ Display user-friendly error message
|
|
||||||
- ✅ Allow dismissing error modal
|
|
||||||
- ✅ No raw API error responses shown
|
|
||||||
- ✅ Prevent retry with same bad checksum
|
|
||||||
- ✅ Maintain single modal through state changes
|
|
||||||
- ✅ Show specific checksum error details
|
|
||||||
|
|
||||||
**Tests**: 8 test cases
|
|
||||||
**Key Feature**: Catches the v4.28.0 duplicate error modal issue
|
|
||||||
|
|
||||||
##### 03. Rate Limiting (`03-rate-limiting.spec.ts`)
|
|
||||||
- ✅ Rate limit excessive update check requests
|
|
||||||
- ✅ Include rate limit headers in response
|
|
||||||
- ✅ Include Retry-After header when rate limited
|
|
||||||
- ✅ Allow requests after rate limit window expires
|
|
||||||
- ✅ Rate limit per IP address independently
|
|
||||||
- ✅ Provide clear error message when rate limited
|
|
||||||
- ✅ Don't rate limit reasonable request patterns
|
|
||||||
- ✅ Rate limit apply update endpoint separately
|
|
||||||
- ✅ Decrement rate limit counter appropriately
|
|
||||||
|
|
||||||
**Tests**: 9 test cases
|
|
||||||
|
|
||||||
##### 04. Network Failure (`04-network-failure.spec.ts`)
|
|
||||||
- ✅ Retry failed update check requests
|
|
||||||
- ✅ Use exponential backoff for retries
|
|
||||||
- ✅ Show loading state during retry
|
|
||||||
- ✅ Eventually succeed after transient failures
|
|
||||||
- ✅ Don't retry indefinitely
|
|
||||||
- ✅ Show error after max retries exceeded
|
|
||||||
- ✅ Handle timeout during download
|
|
||||||
- ✅ Use exponential backoff with maximum cap
|
|
||||||
- ✅ Preserve user context during retries
|
|
||||||
- ✅ Handle partial download failures gracefully
|
|
||||||
|
|
||||||
**Tests**: 10 test cases
|
|
||||||
|
|
||||||
##### 05. Stale Release (`05-stale-release.spec.ts`)
|
|
||||||
- ✅ Reject stale release during download
|
|
||||||
- ✅ Detect stale release before extraction
|
|
||||||
- ✅ Provide informative message about rejection
|
|
||||||
- ✅ Don't create backup for stale release
|
|
||||||
- ✅ Reject stale release even with valid checksum
|
|
||||||
- ✅ Log stale release rejection attempt
|
|
||||||
- ✅ Handle X-Release-Status header from server
|
|
||||||
- ✅ Allow checking for other updates after rejection
|
|
||||||
- ✅ Differentiate stale release error from other errors
|
|
||||||
- ✅ Prevent installation of specific flagged version
|
|
||||||
|
|
||||||
**Tests**: 10 test cases
|
|
||||||
|
|
||||||
##### 06. Frontend Validation (`06-frontend-validation.spec.ts`)
|
|
||||||
- ✅ UpdateProgressModal appears exactly once during update ⭐
|
|
||||||
- ✅ No duplicate modals during state transitions ⭐
|
|
||||||
- ✅ Error modal appears exactly once on checksum failure ⭐
|
|
||||||
- ✅ Error messages are user-friendly (not raw API errors) ⭐
|
|
||||||
- ✅ Modal can be dismissed after error ⭐
|
|
||||||
- ✅ Modal has accessible close button
|
|
||||||
- ✅ ESC key dismisses modal after error
|
|
||||||
- ✅ Error message doesn't contain stack traces
|
|
||||||
- ✅ Error message doesn't contain internal API paths
|
|
||||||
- ✅ Error message is concise and actionable
|
|
||||||
- ✅ Modal has proper ARIA attributes for accessibility
|
|
||||||
- ✅ Progress bar has proper ARIA attributes
|
|
||||||
- ✅ Modal backdrop prevents interaction with background
|
|
||||||
- ✅ Modal maintains focus trap during update
|
|
||||||
- ✅ No console errors during update flow
|
|
||||||
|
|
||||||
**Tests**: 15 test cases
|
|
||||||
**Key Feature**: Comprehensive UX validation to prevent regressions
|
|
||||||
|
|
||||||
### 3. CI/CD Integration
|
|
||||||
|
|
||||||
#### GitHub Actions Workflow (`.github/workflows/test-updates.yml`)
|
|
||||||
- **Triggers**:
|
|
||||||
- Pull requests touching update-related code
|
|
||||||
- Pushes to main/master
|
|
||||||
- Manual workflow dispatch
|
|
||||||
- **Jobs**:
|
|
||||||
- `integration-tests`: Runs all test suites with different configurations
|
|
||||||
- `regression-test`: Verifies tests catch v4.28.0-style checksum issues
|
|
||||||
- **Features**:
|
|
||||||
- Runs each test suite with appropriate mock configuration
|
|
||||||
- Uploads test reports and failure artifacts
|
|
||||||
- Comments on PR when tests fail
|
|
||||||
- Parallel test execution where possible
|
|
||||||
- Automatic cleanup of Docker resources
|
|
||||||
|
|
||||||
### 4. Helper Scripts
|
|
||||||
|
|
||||||
#### Setup Script (`scripts/setup.sh`)
|
|
||||||
- Checks prerequisites (Docker, Node.js, Go)
|
|
||||||
- Installs npm dependencies
|
|
||||||
- Installs Playwright browsers
|
|
||||||
- Builds Docker images
|
|
||||||
- Provides clear setup instructions
|
|
||||||
|
|
||||||
#### Test Runner (`scripts/run-tests.sh`)
|
|
||||||
- Run all tests or specific test suite
|
|
||||||
- Manages Docker environment per test
|
|
||||||
- Provides colored output for test results
|
|
||||||
- Handles cleanup after tests
|
|
||||||
- Reports summary of passed/failed tests
|
|
||||||
|
|
||||||
### 5. Documentation
|
|
||||||
|
|
||||||
#### Main README (`README.md`)
|
|
||||||
- Architecture overview
|
|
||||||
- Test scenario descriptions
|
|
||||||
- Running instructions
|
|
||||||
- Success criteria
|
|
||||||
|
|
||||||
#### Quick Start Guide (`QUICK_START.md`)
|
|
||||||
- Prerequisites
|
|
||||||
- One-time setup
|
|
||||||
- Running tests (all patterns)
|
|
||||||
- Troubleshooting guide
|
|
||||||
- Architecture diagram
|
|
||||||
|
|
||||||
#### Implementation Summary (this document)
|
|
||||||
- Complete overview of what was built
|
|
||||||
- Test coverage statistics
|
|
||||||
- Success criteria verification
|
|
||||||
|
|
||||||
## Test Coverage Statistics
|
|
||||||
|
|
||||||
- **Total Test Files**: 6
|
|
||||||
- **Total Test Cases**: 60+
|
|
||||||
- **Test Scenarios**: 5 major scenarios + frontend validation
|
|
||||||
- **Lines of Test Code**: ~2,500+
|
|
||||||
- **Mock Server Code**: ~300 lines
|
|
||||||
- **Helper Functions**: 20+
|
|
||||||
|
|
||||||
## Success Criteria Verification
|
|
||||||
|
|
||||||
### ✅ Tests run in CI on every PR touching update code
|
|
||||||
**Status**: Implemented in `.github/workflows/test-updates.yml`
|
|
||||||
- Triggers on update-related file changes
|
|
||||||
- Runs automatically on PRs and pushes
|
|
||||||
|
|
||||||
### ✅ All scenarios pass reliably
|
|
||||||
**Status**: Test suite designed for reliability
|
|
||||||
- Each test suite runs in isolated Docker environment
|
|
||||||
- Services have health checks
|
|
||||||
- Proper wait times and timeouts
|
|
||||||
- Cleanup after each test
|
|
||||||
|
|
||||||
### ✅ Tests catch the v4.28.0 checksum issue type automatically
|
|
||||||
**Status**: Specific test coverage implemented
|
|
||||||
- Test suite `02-bad-checksums.spec.ts` specifically validates:
|
|
||||||
- Error appears exactly once (not twice)
|
|
||||||
- No duplicate modals
|
|
||||||
- User-friendly error messages
|
|
||||||
- Regression test job verifies this works
|
|
||||||
|
|
||||||
### ✅ Frontend UX regressions are blocked
|
|
||||||
**Status**: Comprehensive frontend validation suite
|
|
||||||
- Test suite `06-frontend-validation.spec.ts` with 15 test cases
|
|
||||||
- Validates modal behavior, error messages, accessibility
|
|
||||||
- Ensures no duplicate modals in any scenario
|
|
||||||
- Checks for user-friendly error messages
|
|
||||||
- Validates proper ARIA attributes
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
### 1. Controllable Test Environment
|
|
||||||
Environment variables control mock server behavior:
|
|
||||||
```bash
|
|
||||||
MOCK_CHECKSUM_ERROR=true # Return invalid checksums
|
|
||||||
MOCK_NETWORK_ERROR=true # Simulate network failures
|
|
||||||
MOCK_RATE_LIMIT=true # Enable aggressive rate limiting
|
|
||||||
MOCK_STALE_RELEASE=true # Mark releases as stale
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Realistic Mock GitHub Server
|
|
||||||
- Generates actual tarball files with checksums
|
|
||||||
- Simulates GitHub API responses accurately
|
|
||||||
- Provides controllable failure modes
|
|
||||||
- Includes rate limiting
|
|
||||||
- Supports multiple release versions
|
|
||||||
|
|
||||||
### 3. Comprehensive Helper Library
|
|
||||||
20+ helper functions including:
|
|
||||||
- `loginAsAdmin()`, `navigateToSettings()`
|
|
||||||
- `waitForUpdateBanner()`, `clickApplyUpdate()`
|
|
||||||
- `waitForProgressModal()`, `countVisibleModals()`
|
|
||||||
- `assertUserFriendlyError()`, `dismissModal()`
|
|
||||||
- API helpers for direct backend testing
|
|
||||||
|
|
||||||
### 4. CI-Ready
|
|
||||||
- Runs in GitHub Actions
|
|
||||||
- Produces test reports and artifacts
|
|
||||||
- Comments on PRs with results
|
|
||||||
- Verifies regression prevention
|
|
||||||
|
|
||||||
## File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
tests/integration/
|
|
||||||
├── README.md # Main documentation
|
|
||||||
├── QUICK_START.md # Quick start guide
|
|
||||||
├── IMPLEMENTATION_SUMMARY.md # This file
|
|
||||||
├── package.json # npm dependencies
|
|
||||||
├── playwright.config.ts # Playwright configuration
|
|
||||||
├── tsconfig.json # TypeScript configuration
|
|
||||||
├── docker-compose.test.yml # Test environment
|
|
||||||
├── .gitignore # Git ignore rules
|
|
||||||
│
|
|
||||||
├── mock-github-server/ # Mock GitHub API
|
|
||||||
│ ├── main.go # Server implementation
|
|
||||||
│ ├── go.mod # Go dependencies
|
|
||||||
│ └── Dockerfile # Container image
|
|
||||||
│
|
|
||||||
├── scripts/ # Helper scripts
|
|
||||||
│ ├── setup.sh # One-time setup
|
|
||||||
│ └── run-tests.sh # Test runner
|
|
||||||
│
|
|
||||||
└── tests/ # Test suites
|
|
||||||
├── helpers.ts # Test utilities
|
|
||||||
├── 01-happy-path.spec.ts # Happy path tests
|
|
||||||
├── 02-bad-checksums.spec.ts # Checksum validation tests
|
|
||||||
├── 03-rate-limiting.spec.ts # Rate limit tests
|
|
||||||
├── 04-network-failure.spec.ts # Network failure tests
|
|
||||||
├── 05-stale-release.spec.ts # Stale release tests
|
|
||||||
└── 06-frontend-validation.spec.ts # Frontend UX tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running the Tests
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
```bash
|
|
||||||
cd tests/integration
|
|
||||||
./scripts/setup.sh # One-time setup
|
|
||||||
npm test # Run all tests
|
|
||||||
```
|
|
||||||
|
|
||||||
### Specific Scenarios
|
|
||||||
```bash
|
|
||||||
./scripts/run-tests.sh happy # Happy path only
|
|
||||||
./scripts/run-tests.sh checksums # Bad checksums
|
|
||||||
./scripts/run-tests.sh rate-limit # Rate limiting
|
|
||||||
./scripts/run-tests.sh network # Network failures
|
|
||||||
./scripts/run-tests.sh stale # Stale releases
|
|
||||||
./scripts/run-tests.sh frontend # Frontend validation
|
|
||||||
```
|
|
||||||
|
|
||||||
### Interactive Mode
|
|
||||||
```bash
|
|
||||||
npm run test:ui # Playwright UI
|
|
||||||
npm run test:debug # Debug mode
|
|
||||||
npm run test:headed # Headed browser
|
|
||||||
```
|
|
||||||
|
|
||||||
## Technologies Used
|
|
||||||
|
|
||||||
- **Test Framework**: Playwright
|
|
||||||
- **Language**: TypeScript
|
|
||||||
- **Mock Server**: Go
|
|
||||||
- **Container Platform**: Docker & Docker Compose
|
|
||||||
- **CI/CD**: GitHub Actions
|
|
||||||
- **Browser**: Chromium
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
Potential improvements for future iterations:
|
|
||||||
|
|
||||||
1. **Additional Test Scenarios**
|
|
||||||
- Multi-version update paths
|
|
||||||
- Rollback scenarios
|
|
||||||
- Concurrent update attempts
|
|
||||||
- Permission failures
|
|
||||||
|
|
||||||
2. **Performance Testing**
|
|
||||||
- Update download speed
|
|
||||||
- UI responsiveness during update
|
|
||||||
- Backend processing time
|
|
||||||
|
|
||||||
3. **Cross-browser Testing**
|
|
||||||
- Firefox support
|
|
||||||
- Safari/WebKit support
|
|
||||||
|
|
||||||
4. **Test Data Variations**
|
|
||||||
- Different release sizes
|
|
||||||
- Various network speeds
|
|
||||||
- Different update channels (stable vs RC)
|
|
||||||
|
|
||||||
5. **Monitoring Integration**
|
|
||||||
- Test metrics dashboard
|
|
||||||
- Failure trend analysis
|
|
||||||
- Performance benchmarks
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
This implementation provides a robust, comprehensive testing framework for the Pulse update flow that:
|
|
||||||
|
|
||||||
✅ Catches critical issues like the v4.28.0 duplicate modal bug
|
|
||||||
✅ Validates frontend UX to prevent regressions
|
|
||||||
✅ Tests backend logic thoroughly
|
|
||||||
✅ Runs automatically in CI
|
|
||||||
✅ Is easy to run locally
|
|
||||||
✅ Is well-documented
|
|
||||||
✅ Is maintainable and extensible
|
|
||||||
|
|
||||||
The test suite meets all success criteria and provides confidence that update flow changes won't introduce regressions.
|
|
||||||
Loading…
Reference in a new issue