1+ name : Integration Tests
2+
3+ on :
4+ issue_comment :
5+ types : [created]
6+ workflow_dispatch :
7+ # Allow manual trigger for testing
8+
9+
10+ permissions :
11+ contents : read
12+ pull-requests : read
13+ issues : write
14+
15+ jobs :
16+ integration-tests :
17+ runs-on : ubuntu-latest
18+ strategy :
19+ fail-fast : false
20+ matrix :
21+ python_ver : ["3.10", "3.11", "3.12", "3.13"]
22+ # Only run if comment contains "/ok-to-test" and is on a PR
23+ # Also check that commenter is a maintainer/owner
24+ # Or if it's a manual workflow_dispatch
25+ if : |
26+ (github.event_name == 'issue_comment' &&
27+ github.event.issue.pull_request &&
28+ contains(github.event.comment.body, '/ok-to-test') &&
29+ (github.event.comment.author_association == 'OWNER' ||
30+ github.event.comment.author_association == 'MEMBER' ||
31+ github.event.comment.author_association == 'COLLABORATOR')) ||
32+ github.event_name == 'pull_request' ||
33+ github.event_name == 'workflow_dispatch'
34+ steps :
35+ - name : Get PR details
36+ if : github.event_name == 'issue_comment'
37+ id : pr
38+ uses : actions/github-script@v7
39+ with :
40+ script : |
41+ const pr = context.payload.issue.pull_request;
42+ if (!pr) {
43+ core.setFailed('Not a pull request');
44+ return;
45+ }
46+ const { data } = await github.rest.pulls.get({
47+ owner: context.repo.owner,
48+ repo: context.repo.repo,
49+ pull_number: context.payload.issue.number,
50+ });
51+ core.setOutput('head_sha', data.head.sha);
52+ core.setOutput('head_ref', data.head.ref);
53+ core.setOutput('head_repo', data.head.repo.full_name);
54+
55+ - name : Checkout PR
56+ if : github.event_name == 'issue_comment'
57+ uses : actions/checkout@v4
58+ with :
59+ ref : ${{ steps.pr.outputs.head_sha }}
60+ repository : ${{ steps.pr.outputs.head_repo }}
61+
62+ - name : Checkout
63+ if : github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
64+ uses : actions/checkout@v4
65+
66+ - name : Set up Python ${{ matrix.python_ver }}
67+ uses : actions/setup-python@v5
68+ with :
69+ python-version : ${{ matrix.python_ver }}
70+
71+ - name : Set up Docker
72+ uses : docker/setup-docker-action@v3
73+
74+ - name : Install Dapr CLI
75+ run : |
76+ wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
77+
78+ - name : Initialize Dapr
79+ run : |
80+ dapr init
81+
82+ - name : Install uv
83+ uses : astral-sh/setup-uv@v3
84+ with :
85+ version : " latest"
86+
87+ - name : Install dependencies
88+ run : |
89+ uv venv
90+ source .venv/bin/activate
91+ uv pip install -e .
92+ uv pip install -e ".[test]"
93+
94+ - name : Verify secrets are available
95+ run : |
96+ if [ -z "${{ secrets.OPENAI_API_KEY }}" ]; then
97+ echo "OPENAI_API_KEY secret is not available"
98+ echo "This is expected for PRs from forks (GitHub doesn't expose secrets to forks for security)"
99+ echo "For PRs from the same repository, check that secrets are configured in repository settings"
100+ else
101+ echo "OPENAI_API_KEY is available"
102+ fi
103+
104+ if [ -z "${{ secrets.ELEVENLABS_API_KEY }}" ]; then
105+ echo "ELEVENLABS_API_KEY secret is not available"
106+ else
107+ echo "ELEVENLABS_API_KEY is available"
108+ fi
109+
110+ if [ -z "${{ secrets.NVIDIA_API_KEY }}" ]; then
111+ echo "NVIDIA_API_KEY secret is not available"
112+ else
113+ echo "NVIDIA_API_KEY is available"
114+ fi
115+
116+ - name : Run Integration Tests
117+ env :
118+ OPENAI_API_KEY : ${{ secrets.OPENAI_API_KEY }}
119+ run : |
120+ source .venv/bin/activate
121+ pytest -m integration -v tests/integration/quickstarts/test_01_hello_world.py::TestHelloWorldQuickstart::test_basic_llm
122+ # Run all integration tests in parallel (one file per quickstart directory)
123+ # -n auto uses all available CPUs, or specify -n 4 for fixed number
124+ # pytest -m integration -v -n auto tests/integration/quickstarts/
125+
126+ # Summary job that runs after all matrix jobs complete and comments on PR
127+ comment-summary :
128+ needs : integration-tests
129+ runs-on : ubuntu-latest
130+ if : always() && github.event_name == 'issue_comment'
131+ steps :
132+ - name : Comment PR with test results summary
133+ uses : actions/github-script@v7
134+ with :
135+ script : |
136+ const prNumber = context.payload.issue.number;
137+ const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
138+ owner: context.repo.owner,
139+ repo: context.repo.repo,
140+ run_id: context.runId,
141+ });
142+
143+ // Get all integration test jobs (one per Python version from matrix)
144+ const testJobs = jobs.jobs.filter(job => job.name === 'integration-tests');
145+
146+ // Build summary table
147+ let summary = '## Integration Tests Summary\n\n';
148+ summary += '| Python Version | Status |\n';
149+ summary += '|----------------|--------|\n';
150+
151+ let allPassed = true;
152+ const pythonVersions = ['3.10', '3.11', '3.12', '3.13'];
153+
154+ // Sort jobs by Python version order
155+ for (const pythonVer of pythonVersions) {
156+ const job = testJobs.find(j =>
157+ j.steps.some(step => step.name && step.name.includes(pythonVer))
158+ ) || testJobs[pythonVersions.indexOf(pythonVer)];
159+
160+ if (job) {
161+ const status = job.conclusion === 'success' ? '✅ PASSED' : '❌ FAILED';
162+ const jobUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/job/${job.id}`;
163+ summary += `| ${pythonVer} | [${status}](${jobUrl}) |\n`;
164+ if (job.conclusion !== 'success') {
165+ allPassed = false;
166+ }
167+ }
168+ }
169+
170+ summary += `\n**Overall Status:** ${allPassed ? '✅ All tests passed' : '❌ Some tests failed'}\n`;
171+ summary += `**Workflow:** [View run](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})\n`;
172+
173+ // Find existing comment or create new one
174+ const { data: comments } = await github.rest.issues.listComments({
175+ owner: context.repo.owner,
176+ repo: context.repo.repo,
177+ issue_number: prNumber,
178+ });
179+
180+ const existingComment = comments.find(c =>
181+ c.user.type === 'Bot' &&
182+ c.body.includes('## Integration Tests Summary')
183+ );
184+
185+ if (existingComment) {
186+ await github.rest.issues.updateComment({
187+ owner: context.repo.owner,
188+ repo: context.repo.repo,
189+ comment_id: existingComment.id,
190+ body: summary,
191+ });
192+ } else {
193+ await github.rest.issues.createComment({
194+ owner: context.repo.owner,
195+ repo: context.repo.repo,
196+ issue_number: prNumber,
197+ body: summary,
198+ });
199+ }
200+
0 commit comments