Skip to content

Integration Tests

Integration Tests #187

# Integration Tests Workflow
#
# USAGE INSTRUCTIONS:
# 1. Get the exact commit SHA you want to test (must be in the PR)
# 2. Comment on the PR with: /run-integration-tests <SHA>
# 3. The workflow will checkout that specific commit and run integration tests
name: Integration Tests
on:
issue_comment:
types: [created]
jobs:
check-authorization:
name: Check Authorization
# Only run this workflow when someone comments '/run-integration-tests' in a PR
if: |
startsWith(github.event.comment.body, '/run-integration-tests') &&
contains(toJSON(github.event.issue), 'pull_request')
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
# These outputs will be used by other jobs
outputs:
authorized: ${{ steps.auth-check.outputs.authorized }}
pr_number: ${{ github.event.issue.number }}
# The commit SHA extracted from the comment
commit_sha: ${{ steps.extract-sha.outputs.result }}
# Whether a valid SHA was found in the comment
has_sha: ${{ steps.extract-sha.outputs.result != '' }}
steps:
- uses: actions/checkout@v5
- name: Extract commit SHA from comment
id: extract-sha
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const commentBody = context.payload.comment.body;
// Look for SHA pattern after '/run-integration-tests' command
// SHA must be a 40-character hex string
const shaMatch = commentBody.match(/\/run-integration-tests\s+([0-9a-f]{40})(?:\s|$)/i);
if (shaMatch && shaMatch[1]) {
const sha = shaMatch[1];
console.log(`Found SHA in comment: ${sha}`);
// Verify the SHA belongs to this PR
try {
// Get PR number from the issue
const prNumber = context.payload.issue.number;
// Get list of commits in the PR
const { data: commits } = await github.rest.pulls.listCommits({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
// Check if the provided SHA exists in this PR's commits
const commitExists = commits.some(commit => commit.sha === sha);
if (commitExists) {
console.log(`Verified SHA ${sha} belongs to PR #${prNumber}`);
core.setOutput('has_sha', 'true');
return sha; // Return the SHA directly as the result
} else {
console.log(`Error: SHA ${sha} does not belong to PR #${prNumber}`);
console.log('The SHA must be from a commit in the current PR.');
core.setOutput('has_sha', 'false');
return ''; // Return empty string if SHA not in PR
}
} catch (error) {
console.error(`Error validating SHA: ${error.message}`);
core.setOutput('has_sha', 'false');
return ''; // Return empty string on error
}
} else {
console.log(`No valid SHA found in comment. Format should be: /run-integration-tests <40-character-SHA>`);
core.setOutput('has_sha', 'false');
return ''; // Return empty string if no SHA found
}
- name: Check CODEOWNERS authorization
id: auth-check
uses: actions/github-script@v7
with:
script: |
const commenter = context.payload.comment.user.login;
console.log(`Comment by: ${commenter}`);
// Verify this is a pull request comment
if (!context.payload.issue.pull_request) {
console.log('This comment is not on a pull request. Skipping.');
core.setOutput("is_authorized", "false");
return;
}
try {
// Get CODEOWNERS file
const { data: codeownersFile } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: '.github/CODEOWNERS',
});
// Decode content from base64
const content = Buffer.from(codeownersFile.content, 'base64')
.toString('utf8');
// Parse CODEOWNERS file
const lines = content.split('\n')
.filter(line => line.trim() && !line.startsWith('#'));
// Extract owners
const owners = new Set();
for (const line of lines) {
const matches = line.match(/@[\w-]+(?:\/[\w-]+)?/g) || [];
for (const match of matches) {
owners.add(match.substring(1));
}
}
// Check if commenter is in CODEOWNERS
const isAuthorized =
owners.has(commenter) ||
Array.from(owners).some(owner =>
owner.includes('/') &&
owner.startsWith(context.repo.owner + '/')
);
// Output authorization result as a simple string
core.setOutput("authorized", isAuthorized ? "true" : "false");
console.log(`Authorization result: ${isAuthorized}`);
} catch (error) {
console.error(`Error processing CODEOWNERS: ${error.message}`);
core.setOutput("authorized", "false");
}
integration-tests:
name: Run Integration Tests
needs: check-authorization
# Only run if the user is authorized AND provided a valid SHA
if: |
needs.check-authorization.outputs.authorized == 'true' &&
needs.check-authorization.outputs.commit_sha != ''
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
checks: write
env:
# Chronicle config variables
CHRONICLE_CUSTOMER_ID: ${{ secrets.CHRONICLE_CUSTOMER_ID }}
CHRONICLE_PROJECT_NUMBER: ${{ secrets.CHRONICLE_PROJECT_NUMBER }}
CHRONICLE_REGION: ${{ vars.CHRONICLE_REGION }}
# Service account variables
CHRONICLE_PROJECT_NAME: ${{ secrets.CHRONICLE_PROJECT_NAME }}
CHRONICLE_PRIVATE_KEY_ID: ${{ secrets.CHRONICLE_PRIVATE_KEY_ID }}
CHRONICLE_PRIVATE_KEY: ${{ secrets.CHRONICLE_PRIVATE_KEY }}
CHRONICLE_CLIENT_EMAIL: ${{ secrets.CHRONICLE_CLIENT_EMAIL }}
CHRONICLE_CLIENT_ID: ${{ secrets.CHRONICLE_CLIENT_ID }}
CHRONICLE_AUTH_URI: ${{ vars.CHRONICLE_AUTH_URI }}
CHRONICLE_TOKEN_URI: ${{ vars.CHRONICLE_TOKEN_URI }}
CHRONICLE_AUTH_PROVIDER_CERT_URL: ${{ vars.CHRONICLE_AUTH_PROVIDER_CERT_URL }}
CHRONICLE_CLIENT_X509_CERT_URL: ${{ secrets.CHRONICLE_CLIENT_X509_CERT_URL }}
CHRONICLE_UNIVERSE_DOMAIN: ${{ vars.CHRONICLE_UNIVERSE_DOMAIN }}
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
ref: ${{ needs.check-authorization.outputs.commit_sha }}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[test]"
pip install pytest pytest-cov python-dotenv
- name: Authenticate with Google Cloud
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.SERVICE_ACCOUNT_JSON }}
- name: Create in-progress check
id: create-check
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
result-encoding: string
script: |
// Use the SHA specified in the comment, not the PR head SHA
const sha = '${{ needs.check-authorization.outputs.commit_sha }}';
const run_id = process.env.GITHUB_RUN_ID;
const run_url = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${run_id}`;
// Create a check
const check = await github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'Integration Tests',
head_sha: sha,
status: 'in_progress',
output: {
title: 'Integration Tests Running',
summary: `Running integration tests for commit: ${sha.substring(0, 8)}...`,
text: `Integration tests were triggered by comment and are now running on commit ${sha}.\n\n[View Action Details](${run_url})`
}
});
return check.data.id;
- name: Run integration tests
id: run-tests
continue-on-error: true
run: |
echo "Running integration tests..."
python -m pytest tests/ -m "integration" -v --junitxml=junit/integration-test-results.xml
- name: Upload integration test results
uses: actions/upload-artifact@v4
if: always()
with:
name: integration-test-results
path: junit/integration-test-results.xml
retention-days: 1
- name: Publish integration test results
uses: EnricoMi/publish-unit-test-result-action@v2
if: always()
with:
files: junit/integration-test-results.xml
commit: ${{ needs.check-authorization.outputs.commit_sha }}
comment_mode: off
- name: Update check with test results
if: always()
uses: actions/github-script@v7
env:
CHECK_ID: ${{ steps.create-check.outputs.result }}
TEST_OUTCOME: ${{ steps.run-tests.outcome }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const sha = '${{ needs.check-authorization.outputs.commit_sha }}';
const run_id = process.env.GITHUB_RUN_ID;
const run_url = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${run_id}`;
// Determine test status
const testStatus = process.env.TEST_OUTCOME;
let checkConclusion, statusText, statusEmoji;
if (testStatus === 'success') {
checkConclusion = 'success';
statusText = 'Passed';
statusEmoji = '✅';
} else if (testStatus === 'failure') {
checkConclusion = 'failure';
statusText = 'Failed';
statusEmoji = '❌';
} else {
checkConclusion = 'neutral';
statusText = 'Skipped or Cancelled';
statusEmoji = '⚠️';
}
const checkId = process.env.CHECK_ID;
const shortSha = sha.substring(0, 8);
await github.rest.checks.update({
owner: context.repo.owner,
repo: context.repo.repo,
check_run_id: checkId,
status: 'completed',
conclusion: checkConclusion,
output: {
title: `Integration Tests ${statusText} for ${shortSha}`,
summary: `Integration tests for commit ${shortSha} ${statusText.toLowerCase()}.`,
text: `Integration tests for commit ${sha} ${statusText.toLowerCase()}. ${statusEmoji}\n\n[View Action Details](${run_url})`
}
});