Add learner environment integration tests
- test-learner-env.sh: SSH, nix-ld, Slack tokens, Python, API connectivity - test-slack-bolt.py: Socket Mode connection test - Makefile: test runner with env/slack-bolt/vscode targets - Add python3 + uv to system packages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
21c356979e
commit
d387b0b910
|
|
@ -24,6 +24,9 @@
|
|||
git
|
||||
htop
|
||||
curl
|
||||
# Python for learner bot development
|
||||
python3
|
||||
uv
|
||||
];
|
||||
|
||||
# Enable Nix flakes
|
||||
|
|
|
|||
55
tests/Makefile
Normal file
55
tests/Makefile
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Integration tests for learner dev environment
|
||||
# Run from repo root: make -C tests <target>
|
||||
|
||||
SERVER := 45.77.205.49
|
||||
USER := dantest
|
||||
|
||||
.PHONY: all env slack-bolt vscode help
|
||||
|
||||
help:
|
||||
@echo "Learner Environment Integration Tests"
|
||||
@echo ""
|
||||
@echo "Usage: make -C tests <target> [USER=username]"
|
||||
@echo ""
|
||||
@echo "Targets:"
|
||||
@echo " env - Test learner environment (SSH, tokens, Python)"
|
||||
@echo " slack-bolt - Test slack-bolt Socket Mode connection"
|
||||
@echo " vscode - Test VS Code Remote-SSH compatibility"
|
||||
@echo " all - Run all tests"
|
||||
@echo ""
|
||||
@echo "Examples:"
|
||||
@echo " make -C tests env"
|
||||
@echo " make -C tests env USER=alice"
|
||||
@echo " make -C tests all"
|
||||
|
||||
all: env slack-bolt
|
||||
|
||||
env:
|
||||
@./test-learner-env.sh $(USER)
|
||||
|
||||
slack-bolt:
|
||||
@echo "Testing slack-bolt on server as $(USER)..."
|
||||
@ssh $(USER)@$(SERVER) 'source /etc/slack-learner.env && \
|
||||
uv pip install --quiet slack-bolt 2>/dev/null && \
|
||||
python3 -' < test-slack-bolt.py
|
||||
|
||||
vscode:
|
||||
@echo "Testing VS Code Server binary compatibility..."
|
||||
@echo "This test downloads and runs the VS Code Server binary."
|
||||
@echo ""
|
||||
@ssh $(USER)@$(SERVER) 'set -e; \
|
||||
VSCODE_TEST_DIR=$$(mktemp -d); \
|
||||
cd $$VSCODE_TEST_DIR; \
|
||||
echo "Downloading VS Code Server CLI..."; \
|
||||
curl -sL "https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64" -o vscode-cli.tar.gz; \
|
||||
tar -xzf vscode-cli.tar.gz; \
|
||||
echo "Testing binary execution..."; \
|
||||
if ./code --version >/dev/null 2>&1; then \
|
||||
echo "PASS: VS Code CLI executes (nix-ld working)"; \
|
||||
./code --version | head -1; \
|
||||
else \
|
||||
echo "FAIL: VS Code CLI failed to execute"; \
|
||||
echo " nix-ld may not be configured correctly"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
rm -rf $$VSCODE_TEST_DIR'
|
||||
197
tests/test-learner-env.sh
Executable file
197
tests/test-learner-env.sh
Executable file
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env bash
|
||||
# test-learner-env.sh - Integration tests for learner dev environment
|
||||
# Run from local machine: ./tests/test-learner-env.sh [username] [ssh-key]
|
||||
# Default username: dantest
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SERVER="45.77.205.49"
|
||||
USER="${1:-dantest}"
|
||||
SSH_KEY="${2:-}"
|
||||
|
||||
# Build SSH options
|
||||
SSH_OPTS="-o ConnectTimeout=5"
|
||||
if [ -n "$SSH_KEY" ]; then
|
||||
SSH_OPTS="$SSH_OPTS -i $SSH_KEY"
|
||||
fi
|
||||
|
||||
ssh_cmd() {
|
||||
ssh $SSH_OPTS "${USER}@${SERVER}" "$@"
|
||||
}
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
red() { echo -e "\033[0;31m$1\033[0m"; }
|
||||
green() { echo -e "\033[0;32m$1\033[0m"; }
|
||||
yellow() { echo -e "\033[1;33m$1\033[0m"; }
|
||||
|
||||
pass() { PASS=$((PASS + 1)); green " PASS: $1"; }
|
||||
fail() { FAIL=$((FAIL + 1)); red " FAIL: $1"; }
|
||||
skip() { yellow " SKIP: $1"; }
|
||||
|
||||
echo "=========================================="
|
||||
echo "Learner Environment Tests"
|
||||
echo "Server: $SERVER"
|
||||
echo "User: $USER"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# ---------------------------------------------
|
||||
echo "## 1. SSH Connectivity"
|
||||
# ---------------------------------------------
|
||||
if ssh_cmd 'echo ok' &>/dev/null; then
|
||||
pass "SSH connection as $USER"
|
||||
else
|
||||
fail "SSH connection as $USER"
|
||||
echo " Hint: Check SSH key is configured for $USER"
|
||||
echo " Usage: $0 <user> [/path/to/key]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------
|
||||
echo ""
|
||||
echo "## 2. nix-ld (VS Code Remote-SSH support)"
|
||||
# ---------------------------------------------
|
||||
if ssh_cmd 'test -L /lib64/ld-linux-x86-64.so.2' &>/dev/null; then
|
||||
pass "nix-ld symlink exists"
|
||||
else
|
||||
fail "nix-ld symlink missing"
|
||||
fi
|
||||
|
||||
# Test with a simple pre-compiled binary (curl is dynamically linked)
|
||||
if ssh_cmd 'file /run/current-system/sw/bin/curl | grep -q "dynamically linked"' &>/dev/null; then
|
||||
pass "Dynamic binaries available"
|
||||
else
|
||||
skip "Could not verify dynamic binary support"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------
|
||||
echo ""
|
||||
echo "## 3. Learners group membership"
|
||||
# ---------------------------------------------
|
||||
if ssh_cmd 'groups' | grep -q learners; then
|
||||
pass "User in learners group"
|
||||
else
|
||||
fail "User NOT in learners group"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------
|
||||
echo ""
|
||||
echo "## 4. Slack tokens accessible"
|
||||
# ---------------------------------------------
|
||||
if ssh_cmd 'test -r /etc/slack-learner.env'; then
|
||||
pass "Slack env file readable"
|
||||
else
|
||||
fail "Slack env file NOT readable (check group permissions)"
|
||||
fi
|
||||
|
||||
if ssh_cmd 'source /etc/slack-learner.env && test -n "$SLACK_BOT_TOKEN"' &>/dev/null; then
|
||||
pass "SLACK_BOT_TOKEN set"
|
||||
else
|
||||
fail "SLACK_BOT_TOKEN not set"
|
||||
fi
|
||||
|
||||
if ssh_cmd 'source /etc/slack-learner.env && test -n "$SLACK_APP_TOKEN"' &>/dev/null; then
|
||||
pass "SLACK_APP_TOKEN set"
|
||||
else
|
||||
fail "SLACK_APP_TOKEN not set"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------
|
||||
echo ""
|
||||
echo "## 5. Python environment"
|
||||
# ---------------------------------------------
|
||||
if ssh_cmd 'which python3' &>/dev/null; then
|
||||
pass "python3 available"
|
||||
PY_VERSION=$(ssh_cmd 'python3 --version 2>&1')
|
||||
echo " $PY_VERSION"
|
||||
else
|
||||
fail "python3 not found"
|
||||
fi
|
||||
|
||||
if ssh_cmd 'uv --version' &>/dev/null; then
|
||||
UV_VERSION=$(ssh_cmd 'uv --version 2>&1')
|
||||
pass "uv available ($UV_VERSION)"
|
||||
else
|
||||
fail "uv not available"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------
|
||||
echo ""
|
||||
echo "## 6. Home directory structure"
|
||||
# ---------------------------------------------
|
||||
if ssh_cmd 'test -d ~/plugins'; then
|
||||
pass "~/plugins directory exists"
|
||||
else
|
||||
fail "~/plugins directory missing"
|
||||
fi
|
||||
|
||||
if ssh_cmd 'test -d ~/plugins/hello-*' &>/dev/null; then
|
||||
pass "Starter plugin exists"
|
||||
else
|
||||
skip "No starter plugin (may be expected)"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------
|
||||
echo ""
|
||||
echo "## 7. Slack API connectivity"
|
||||
# ---------------------------------------------
|
||||
echo " Testing Slack API auth (this may take a moment)..."
|
||||
|
||||
SLACK_TEST=$(ssh_cmd 'source /etc/slack-learner.env && python3 -c "
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import json
|
||||
import os
|
||||
|
||||
token = os.environ.get(\"SLACK_BOT_TOKEN\", \"\")
|
||||
if not token:
|
||||
print(\"NO_TOKEN\")
|
||||
exit(1)
|
||||
|
||||
req = urllib.request.Request(
|
||||
\"https://slack.com/api/auth.test\",
|
||||
headers={\"Authorization\": f\"Bearer {token}\"}
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
data = json.loads(resp.read())
|
||||
if data.get(\"ok\"):
|
||||
user = data.get(\"user\", \"unknown\")
|
||||
team = data.get(\"team\", \"unknown\")
|
||||
print(f\"OK:{user}@{team}\")
|
||||
else:
|
||||
err = data.get(\"error\", \"unknown\")
|
||||
print(f\"API_ERROR:{err}\")
|
||||
except Exception as e:
|
||||
print(f\"NET_ERROR:{e}\")
|
||||
" 2>&1' || echo "EXEC_ERROR")
|
||||
|
||||
case "$SLACK_TEST" in
|
||||
OK:*)
|
||||
pass "Slack API auth successful"
|
||||
echo " Authenticated as: ${SLACK_TEST#OK:}"
|
||||
;;
|
||||
API_ERROR:*)
|
||||
fail "Slack API error: ${SLACK_TEST#API_ERROR:}"
|
||||
;;
|
||||
NET_ERROR:*)
|
||||
fail "Network error: ${SLACK_TEST#NET_ERROR:}"
|
||||
;;
|
||||
NO_TOKEN)
|
||||
fail "No SLACK_BOT_TOKEN available"
|
||||
;;
|
||||
*)
|
||||
fail "Unexpected result: $SLACK_TEST"
|
||||
;;
|
||||
esac
|
||||
|
||||
# ---------------------------------------------
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
echo "=========================================="
|
||||
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
94
tests/test-slack-bolt.py
Executable file
94
tests/test-slack-bolt.py
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test slack-bolt can connect via Socket Mode.
|
||||
|
||||
Run on server as learner user:
|
||||
python3 tests/test-slack-bolt.py
|
||||
|
||||
Requires: pip install slack-bolt
|
||||
Expects: SLACK_BOT_TOKEN and SLACK_APP_TOKEN in environment
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
|
||||
def main():
|
||||
# Check tokens
|
||||
bot_token = os.environ.get("SLACK_BOT_TOKEN")
|
||||
app_token = os.environ.get("SLACK_APP_TOKEN")
|
||||
|
||||
if not bot_token:
|
||||
print("FAIL: SLACK_BOT_TOKEN not set")
|
||||
print(" Hint: source /etc/slack-learner.env")
|
||||
sys.exit(1)
|
||||
|
||||
if not app_token:
|
||||
print("FAIL: SLACK_APP_TOKEN not set")
|
||||
print(" Hint: source /etc/slack-learner.env")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Bot token: {bot_token[:10]}...{bot_token[-4:]}")
|
||||
print(f"App token: {app_token[:10]}...{app_token[-4:]}")
|
||||
|
||||
# Try importing slack-bolt
|
||||
try:
|
||||
from slack_bolt import App
|
||||
from slack_bolt.adapter.socket_mode import SocketModeHandler
|
||||
print("PASS: slack-bolt imported")
|
||||
except ImportError as e:
|
||||
print(f"FAIL: Cannot import slack-bolt: {e}")
|
||||
print(" Hint: pip install slack-bolt")
|
||||
sys.exit(1)
|
||||
|
||||
# Create app
|
||||
print("Creating Slack app...")
|
||||
app = App(token=bot_token)
|
||||
|
||||
# Track connection
|
||||
connected = threading.Event()
|
||||
error_msg = None
|
||||
|
||||
@app.event("app_home_opened")
|
||||
def handle_home(event, logger):
|
||||
# This won't fire in test, just proves handler registration works
|
||||
pass
|
||||
|
||||
print("Starting Socket Mode connection (5 second test)...")
|
||||
|
||||
def run_socket():
|
||||
nonlocal error_msg
|
||||
try:
|
||||
handler = SocketModeHandler(app, app_token)
|
||||
# Start in background, will connect
|
||||
handler.connect()
|
||||
connected.set()
|
||||
time.sleep(5)
|
||||
handler.close()
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
connected.set()
|
||||
|
||||
thread = threading.Thread(target=run_socket)
|
||||
thread.start()
|
||||
|
||||
# Wait for connection or timeout
|
||||
connected.wait(timeout=10)
|
||||
|
||||
if error_msg:
|
||||
print(f"FAIL: Socket Mode error: {error_msg}")
|
||||
sys.exit(1)
|
||||
|
||||
if connected.is_set():
|
||||
print("PASS: Socket Mode connected successfully")
|
||||
print("")
|
||||
print("All tests passed. Bot is ready for development.")
|
||||
thread.join(timeout=2)
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("FAIL: Socket Mode connection timed out")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue