Skip to content

Commit 73e446a

Browse files
authored
Merge pull request #166 from thestumonkey/workflow
added git workflow
2 parents 543fcea + ec76037 commit 73e446a

1 file changed

Lines changed: 345 additions & 0 deletions

File tree

‎.github/workflows/robot-tests.yml‎

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
name: Robot Framework Tests
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'tests/**/*.robot'
7+
- 'tests/**/*.py'
8+
- 'backends/advanced/src/**'
9+
- '.github/workflows/robot-tests.yml'
10+
11+
permissions:
12+
contents: read
13+
pull-requests: write
14+
issues: write
15+
pages: write
16+
id-token: write
17+
18+
jobs:
19+
robot-tests:
20+
runs-on: ubuntu-latest
21+
timeout-minutes: 30
22+
23+
steps:
24+
- name: Checkout code
25+
uses: actions/checkout@v4
26+
27+
- name: Set up Docker Buildx
28+
uses: docker/setup-buildx-action@v3
29+
with:
30+
driver-opts: |
31+
image=moby/buildkit:latest
32+
network=host
33+
34+
- name: Cache Docker layers
35+
uses: actions/cache@v4
36+
with:
37+
path: /tmp/.buildx-cache
38+
key: ${{ runner.os }}-buildx-${{ hashFiles('backends/advanced/Dockerfile', 'backends/advanced/pyproject.toml') }}
39+
restore-keys: |
40+
${{ runner.os }}-buildx-
41+
42+
- name: Set up Python
43+
uses: actions/setup-python@v5
44+
with:
45+
python-version: "3.12"
46+
cache: 'pip'
47+
48+
- name: Install Robot Framework and dependencies
49+
run: |
50+
pip install --upgrade pip
51+
pip install robotframework robotframework-requests python-dotenv websockets
52+
53+
- name: Create test environment file
54+
working-directory: tests/setup
55+
run: |
56+
cat > .env.test << EOF
57+
# API URLs
58+
API_URL=http://localhost:8001
59+
BACKEND_URL=http://localhost:8001
60+
FRONTEND_URL=http://localhost:3001
61+
62+
# Test Admin Credentials
63+
ADMIN_EMAIL=test-admin@example.com
64+
ADMIN_PASSWORD=test-admin-password-123
65+
66+
# API Keys (from GitHub secrets)
67+
OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
68+
DEEPGRAM_API_KEY=${{ secrets.DEEPGRAM_API_KEY }}
69+
70+
# Test Configuration
71+
TEST_TIMEOUT=120
72+
TEST_DEVICE_NAME=robot-test
73+
EOF
74+
75+
- name: Start test environment
76+
working-directory: backends/advanced
77+
env:
78+
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
79+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
80+
LLM_PROVIDER: openai
81+
TRANSCRIPTION_PROVIDER: deepgram
82+
MEMORY_PROVIDER: friend_lite
83+
run: |
84+
# Debug: Check if secrets are available
85+
echo "Checking environment variables..."
86+
echo "DEEPGRAM_API_KEY is set: $([ -n "$DEEPGRAM_API_KEY" ] && echo 'YES' || echo 'NO')"
87+
echo "OPENAI_API_KEY is set: $([ -n "$OPENAI_API_KEY" ] && echo 'YES' || echo 'NO')"
88+
echo "LLM_PROVIDER: $LLM_PROVIDER"
89+
echo "TRANSCRIPTION_PROVIDER: $TRANSCRIPTION_PROVIDER"
90+
91+
# Create memory_config.yaml from template (file is gitignored)
92+
echo "Creating memory_config.yaml from template..."
93+
cp memory_config.yaml.template memory_config.yaml
94+
95+
# Clean any existing test containers for fresh start
96+
echo "Cleaning up any existing test containers..."
97+
docker compose -f docker-compose-test.yml down -v || true
98+
99+
# Start ALL services in parallel - Docker Compose handles dependencies via healthchecks
100+
echo "Starting all services in parallel (docker-compose-test.yml)..."
101+
echo "Note: Using test compose file with source mounts for faster startup"
102+
103+
# Export API keys so docker-compose can use them
104+
export DEEPGRAM_API_KEY
105+
export OPENAI_API_KEY
106+
export LLM_PROVIDER
107+
export TRANSCRIPTION_PROVIDER
108+
export MEMORY_PROVIDER
109+
110+
DOCKER_BUILDKIT=0 docker compose -f docker-compose-test.yml up -d
111+
112+
# Show container status
113+
echo "Container status:"
114+
docker compose -f docker-compose-test.yml ps
115+
116+
# Single wait for backend readiness (backend depends_on ensures infra is ready)
117+
echo "Waiting for backend readiness (up to 120s)..."
118+
for i in {1..40}; do
119+
if curl -s http://localhost:8001/readiness > /dev/null 2>&1; then
120+
echo "✓ Backend is ready (all dependencies satisfied)"
121+
break
122+
fi
123+
# Show logs every 10 attempts to help debug
124+
if [ $((i % 10)) -eq 0 ]; then
125+
echo "Still waiting... showing recent logs:"
126+
docker compose -f docker-compose-test.yml logs --tail=20 friend-backend-test
127+
fi
128+
if [ $i -eq 40 ]; then
129+
echo "✗ Backend failed to start - showing full logs:"
130+
docker compose -f docker-compose-test.yml logs
131+
exit 1
132+
fi
133+
echo "Attempt $i/40..."
134+
sleep 3
135+
done
136+
137+
echo "✓ Backend is ready!"
138+
139+
# Verify workers are registered with Redis (Robot tests need stable workers)
140+
echo "Waiting for workers to register with Redis (up to 60s)..."
141+
for i in {1..30}; do
142+
WORKER_COUNT=$(docker compose -f docker-compose-test.yml exec -T workers-test uv run python -c 'from rq import Worker; from redis import Redis; import os; r = Redis.from_url(os.getenv("REDIS_URL", "redis://redis-test:6379/0")); print(len(Worker.all(connection=r)))' 2>/dev/null || echo "0")
143+
144+
if [ "$WORKER_COUNT" -ge 6 ]; then
145+
echo "✓ Found $WORKER_COUNT workers registered"
146+
# Show worker details
147+
docker compose -f docker-compose-test.yml exec -T workers-test uv run python -c 'from rq import Worker; from redis import Redis; import os; r = Redis.from_url(os.getenv("REDIS_URL", "redis://redis-test:6379/0")); workers = Worker.all(connection=r); print(f"Total registered workers: {len(workers)}"); [print(f" - {w.name}: queues={w.queue_names()}, state={w.get_state()}") for w in workers]'
148+
break
149+
fi
150+
151+
if [ $i -eq 30 ]; then
152+
echo "✗ Workers failed to register after 60s"
153+
echo "Showing worker logs:"
154+
docker compose -f docker-compose-test.yml logs --tail=50 workers-test
155+
exit 1
156+
fi
157+
158+
echo "Attempt $i/30: $WORKER_COUNT workers registered (waiting for 6+)..."
159+
sleep 2
160+
done
161+
162+
echo "✓ All services ready!"
163+
164+
- name: Run Robot Framework tests
165+
working-directory: tests
166+
env:
167+
# Required for backend imports in test libraries
168+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
169+
OPENAI_BASE_URL: https://api.openai.com/v1
170+
OPENAI_MODEL: gpt-4o-mini
171+
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
172+
run: |
173+
# Run all tests
174+
make all OUTPUTDIR=results || true
175+
176+
- name: Show service logs
177+
if: always()
178+
working-directory: backends/advanced
179+
run: |
180+
echo "=== Backend Logs (last 50 lines) ==="
181+
docker compose -f docker-compose-test.yml logs --tail=50 friend-backend-test
182+
echo ""
183+
echo "=== Worker Logs (last 50 lines) ==="
184+
docker compose -f docker-compose-test.yml logs --tail=50 workers-test
185+
186+
- name: Check if test results exist
187+
if: always()
188+
id: check_results
189+
run: |
190+
if [ -f tests/results/output.xml ]; then
191+
echo "results_exist=true" >> $GITHUB_OUTPUT
192+
else
193+
echo "results_exist=false" >> $GITHUB_OUTPUT
194+
echo "⚠️ No test results found in tests/results/"
195+
ls -la tests/results/ || echo "Results directory doesn't exist"
196+
fi
197+
198+
- name: Upload Robot Framework HTML reports
199+
if: always() && steps.check_results.outputs.results_exist == 'true'
200+
uses: actions/upload-artifact@v4
201+
with:
202+
name: robot-test-reports-html
203+
path: |
204+
tests/results/report.html
205+
tests/results/log.html
206+
retention-days: 30
207+
208+
- name: Publish HTML Report as GitHub Pages artifact
209+
if: always() && steps.check_results.outputs.results_exist == 'true'
210+
uses: actions/upload-pages-artifact@v3
211+
with:
212+
path: tests/results
213+
214+
- name: Deploy to GitHub Pages
215+
if: always() && steps.check_results.outputs.results_exist == 'true'
216+
uses: actions/deploy-pages@v4
217+
id: deployment
218+
219+
- name: Generate test summary
220+
if: always() && steps.check_results.outputs.results_exist == 'true'
221+
id: test_summary
222+
run: |
223+
# Parse test results
224+
python3 << 'PYTHON_SCRIPT' > test_summary.txt
225+
import xml.etree.ElementTree as ET
226+
tree = ET.parse('tests/results/output.xml')
227+
root = tree.getroot()
228+
stats = root.find('.//total/stat')
229+
if stats is not None:
230+
passed = stats.get("pass", "0")
231+
failed = stats.get("fail", "0")
232+
total = int(passed) + int(failed)
233+
print(f"PASSED={passed}")
234+
print(f"FAILED={failed}")
235+
print(f"TOTAL={total}")
236+
PYTHON_SCRIPT
237+
238+
# Source the variables
239+
source test_summary.txt
240+
241+
# Set outputs
242+
echo "passed=$PASSED" >> $GITHUB_OUTPUT
243+
echo "failed=$FAILED" >> $GITHUB_OUTPUT
244+
echo "total=$TOTAL" >> $GITHUB_OUTPUT
245+
246+
- name: Post PR comment with test results
247+
if: always() && steps.check_results.outputs.results_exist == 'true'
248+
uses: actions/github-script@v7
249+
with:
250+
github-token: ${{ secrets.GITHUB_TOKEN }}
251+
script: |
252+
const passed = '${{ steps.test_summary.outputs.passed }}';
253+
const failed = '${{ steps.test_summary.outputs.failed }}';
254+
const total = '${{ steps.test_summary.outputs.total }}';
255+
const runUrl = `https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}`;
256+
const pagesUrl = '${{ steps.deployment.outputs.page_url }}';
257+
258+
const status = failed === '0' ? '✅ All tests passed!' : '❌ Some tests failed';
259+
const emoji = failed === '0' ? '🎉' : '⚠️';
260+
261+
const comment = `## ${emoji} Robot Framework Test Results
262+
263+
**Status**: ${status}
264+
265+
| Metric | Count |
266+
|--------|-------|
267+
| ✅ Passed | ${passed} |
268+
| ❌ Failed | ${failed} |
269+
| 📊 Total | ${total} |
270+
271+
### 📊 View Reports
272+
273+
**GitHub Pages (Live Reports):**
274+
- [📋 Test Report](${pagesUrl}report.html)
275+
- [📝 Detailed Log](${pagesUrl}log.html)
276+
277+
**Download Artifacts:**
278+
- [robot-test-reports-html](${runUrl}) - HTML reports
279+
- [robot-test-results-xml](${runUrl}) - XML output
280+
281+
---
282+
*[View full workflow run](${runUrl})*`;
283+
284+
github.rest.issues.createComment({
285+
issue_number: context.issue.number,
286+
owner: context.repo.owner,
287+
repo: context.repo.repo,
288+
body: comment
289+
});
290+
291+
- name: Upload Robot Framework XML output
292+
if: always() && steps.check_results.outputs.results_exist == 'true'
293+
uses: actions/upload-artifact@v4
294+
with:
295+
name: robot-test-results-xml
296+
path: tests/results/output.xml
297+
retention-days: 30
298+
299+
- name: Upload logs on failure
300+
if: failure()
301+
uses: actions/upload-artifact@v4
302+
with:
303+
name: robot-test-logs
304+
path: |
305+
backends/advanced/.env
306+
tests/setup/.env.test
307+
retention-days: 7
308+
309+
- name: Display test results summary
310+
if: always()
311+
run: |
312+
if [ -f tests/results/output.xml ]; then
313+
echo "Test results generated successfully"
314+
echo "========================================"
315+
python3 << 'PYTHON_SCRIPT'
316+
import xml.etree.ElementTree as ET
317+
tree = ET.parse('tests/results/output.xml')
318+
root = tree.getroot()
319+
stats = root.find('.//total/stat')
320+
if stats is not None:
321+
passed = stats.get("pass", "0")
322+
failed = stats.get("fail", "0")
323+
print(f'✅ Passed: {passed}')
324+
print(f'❌ Failed: {failed}')
325+
print(f'📊 Total: {int(passed) + int(failed)}')
326+
PYTHON_SCRIPT
327+
echo "========================================"
328+
echo ""
329+
echo "📊 FULL TEST REPORTS AVAILABLE:"
330+
echo " 1. Go to the 'Summary' tab at the top of this page"
331+
echo " 2. Scroll down to 'Artifacts' section"
332+
echo " 3. Download 'robot-test-reports-html'"
333+
echo " 4. Extract and open report.html or log.html in your browser"
334+
echo ""
335+
echo "The HTML reports provide:"
336+
echo " - report.html: Executive summary with statistics"
337+
echo " - log.html: Detailed step-by-step execution log"
338+
echo ""
339+
fi
340+
341+
- name: Cleanup
342+
if: always()
343+
working-directory: backends/advanced
344+
run: |
345+
docker compose -f docker-compose-test.yml down -v

0 commit comments

Comments
 (0)