Skip to content

Commit da0078f

Browse files
[GitHub Actions] - QA Continuous Integration
1 parent 7a70331 commit da0078f

21 files changed

+5566
-4
lines changed

.github/workflows/qa-scenarios.yml

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
name: QA Scenarios
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'scripts/run-scenarios.cjs'
7+
- 'bruno/Tests/**'
8+
- 'bruno/SIS/**'
9+
- '.github/workflows/qa-scenarios.yml'
10+
push:
11+
branches: [ main ]
12+
paths:
13+
- 'scripts/run-scenarios.cjs'
14+
- 'bruno/Tests/**'
15+
- 'bruno/SIS/**'
16+
workflow_dispatch:
17+
inputs:
18+
env:
19+
description: 'Bruno environment name (default ci.ed-fi.org)'
20+
required: false
21+
default: 'ci.ed-fi.org'
22+
includeSteps:
23+
description: 'Include detailed steps in aggregate summary'
24+
required: false
25+
default: 'false'
26+
27+
permissions:
28+
contents: read
29+
pull-requests: write
30+
actions: read
31+
32+
concurrency:
33+
group: qa-scenarios-${{ github.ref }}
34+
cancel-in-progress: true
35+
36+
jobs:
37+
qa:
38+
runs-on: ubuntu-latest
39+
steps:
40+
- name: Checkout
41+
uses: actions/checkout@v4
42+
43+
- name: Use Node.js 20
44+
uses: actions/setup-node@v4
45+
with:
46+
node-version: 20
47+
cache: 'npm'
48+
cache-dependency-path: bruno/package-lock.json
49+
50+
- name: Mask client secret (if present)
51+
run: |
52+
if [ -f bruno/Tests/environments/ci.ed-fi.org.bru ]; then
53+
SECRET_LINE=$(grep -E 'edFiClientSecret:' bruno/Tests/environments/ci.ed-fi.org.bru || true)
54+
if [ -n "$SECRET_LINE" ]; then
55+
SECRET_VAL=$(echo "$SECRET_LINE" | sed -E 's/.*edFiClientSecret:\s*([^ ]+)/\1/')
56+
echo "::add-mask::$SECRET_VAL"
57+
fi
58+
fi
59+
60+
- name: Run QA Scenarios
61+
id: runqa
62+
env:
63+
BRUNO_ENV: ${{ github.event.inputs.env || 'ci.ed-fi.org' }}
64+
INCLUDE_STEPS: ${{ github.event.inputs.includeSteps || 'false' }}
65+
run: |
66+
set -e
67+
INCLUDE_FLAG=""
68+
if [ "$INCLUDE_STEPS" = "true" ]; then INCLUDE_FLAG="--include-steps"; fi
69+
node scripts/run-scenarios.cjs --env "$BRUNO_ENV" $INCLUDE_FLAG || EXIT=$?
70+
# Preserve non-zero exit for later; script already writes summary.json
71+
if [ -n "$EXIT" ]; then echo "exit_code=$EXIT" >> $GITHUB_OUTPUT; else echo "exit_code=0" >> $GITHUB_OUTPUT; fi
72+
73+
- name: Show aggregate summary
74+
if: always()
75+
run: |
76+
if [ -f automation-testing/results/summary.json ]; then
77+
cat automation-testing/results/summary.json | jq '.' || cat automation-testing/results/summary.json
78+
else
79+
echo "summary.json not found"
80+
fi
81+
82+
- name: Upload QA artifacts
83+
if: always()
84+
uses: actions/upload-artifact@v4
85+
with:
86+
name: qa-scenarios-results
87+
path: |
88+
automation-testing/results/summary.json
89+
automation-testing/results/*.json
90+
automation-testing/results/last-output.txt
91+
if-no-files-found: warn
92+
93+
- name: Comment summary on PR
94+
if: github.event_name == 'pull_request'
95+
uses: actions/github-script@v7
96+
with:
97+
github-token: ${{ secrets.GITHUB_TOKEN }}
98+
script: |
99+
const fs = require('fs');
100+
const summaryPath = 'automation-testing/results/summary.json';
101+
if (!fs.existsSync(summaryPath)) {
102+
github.rest.issues.createComment({
103+
issue_number: context.payload.pull_request.number,
104+
owner: context.repo.owner,
105+
repo: context.repo.repo,
106+
body: ':warning: QA Scenarios did not produce a summary.json file.'
107+
});
108+
} else {
109+
const data = JSON.parse(fs.readFileSync(summaryPath,'utf8'));
110+
const lines = [];
111+
lines.push('### QA Scenarios Summary');
112+
lines.push(`Status: **${data.exitStatus}**`);
113+
lines.push(`Entities Processed: **${data.totals.entitiesProcessed}**`);
114+
lines.push(`Assertions: Passed **${data.totals.assertionsPassed}**, Failed **${data.totals.assertionsFailed}**, Total **${data.totals.assertionsTotal}**`);
115+
if (data.entities) {
116+
lines.push('');
117+
lines.push('| Entity | Passed | Failed | Total |');
118+
lines.push('|--------|--------|--------|-------|');
119+
for (const e of data.entities) {
120+
const a = e.assertions; lines.push(`| ${e.entity} | ${a.passed} | ${a.failed} | ${a.total} |`);
121+
}
122+
}
123+
github.rest.issues.createComment({
124+
issue_number: context.payload.pull_request.number,
125+
owner: context.repo.owner,
126+
repo: context.repo.repo,
127+
body: lines.join('\n')
128+
});
129+
}
130+
131+
- name: Fail if scenarios failed
132+
if: steps.runqa.outputs.exit_code != '0'
133+
run: |
134+
echo "Scenario script exited with code ${EXIT_CODE}"; exit 1

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
# Mac OS Files
22
.DS_Store
3+
4+
# Ignore node_modules directory
5+
node_modules/
6+
7+
# Ignore automation testing generated copies
8+
automation-testing/
9+
10+
# Ignore local artifacts
11+
local-artifacts

bruno/SIS/v4/MasterSchedule/BellSchedules/01 - Check BellSchedule is valid.bru

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ meta {
55
}
66

77
get {
8-
url: {{resourceBaseUrl}}/ed-fi/bellSchedules?schoolId=[ENTER SCHOOL ID]&bellScheduleName=[ENTER BELL SCHEDULE NAME]
8+
url: {{resourceBaseUrl}}/ed-fi/bellSchedules?schoolId=[ENTER_SCHOOL_ID]&bellScheduleName=[ENTER_BELL_SCHEDULE_NAME]
99
body: none
1010
auth: inherit
1111
}
1212

1313
params:query {
14-
schoolId: [ENTER SCHOOL ID]
15-
bellScheduleName: [ENTER BELL SCHEDULE NAME]
14+
schoolId: [ENTER_SCHOOL_ID]
15+
bellScheduleName: [ENTER_BELL_SCHEDULE_NAME]
1616
}
1717

1818
assert {

bruno/Tests/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
EDFI_CLIENT_NAME=PUBLIC_CLIENT
2+
EDFI_CLIENT_ID=RvcohKz9zHI4
3+
EDFI_CLIENT_SECRET=E1iEFusaNf81xzCxwHfbolkC

bruno/Tests/.npmrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package-lock=true
2+
fund=false
3+
audit=false

bruno/Tests/collection.bru

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
script:pre-request {
2+
const edFiClientName = bru.getGlobalEnvVar('edFiClientName') || bru.getEnvVar('edFiClientName');
3+
let generateToken = true;
4+
5+
if (bru.getEnvVar('edFiCertToken') && bru.getEnvVar('edFiCertTokenExpiration')) {
6+
const currentSeconds = new Date().getTime() / 1000;
7+
if (currentSeconds < bru.getEnvVar('edFiCertTokenExpiration')) {
8+
generateToken = false; // Token is still valid, no need to fetch a new one
9+
}
10+
}
11+
12+
if (generateToken) {
13+
console.log(`Fetching new token for "${edFiClientName}"...`);
14+
15+
// Get Credentials from Collection or Global environment variables
16+
const edFiClientId = bru.getGlobalEnvVar('edFiClientId') || bru.getEnvVar('edFiClientId');
17+
const edFiClientSecret = bru.getGlobalEnvVar('edFiClientSecret') || bru.getEnvVar('edFiClientSecret');
18+
19+
if (!edFiClientId || !edFiClientSecret) {
20+
const errorMsg = 'The credentials for edFiClientId or edFiClientSecret were not set yet. Please configure them in your collection or global environment variables.';
21+
console.error(errorMsg);
22+
throw new Error(errorMsg);
23+
}
24+
25+
await bru.sendRequest({
26+
url: `${bru.getEnvVar('oauthUrl')}`,
27+
method: 'POST',
28+
headers: {
29+
"Content-Type": "application/x-www-form-urlencoded"
30+
},
31+
data: {
32+
"grant_type": 'client_credentials',
33+
"client_id": edFiClientId,
34+
"client_secret": edFiClientSecret
35+
}
36+
}, async function(err, res) {
37+
if (err) {
38+
console.error('Error when generating the token:', err);
39+
throw new Error('Error when generating a new token. Please check your credentials in the "Global Environments" or ".env" file, and verify you are using the right "Collection Environment".');
40+
} else {
41+
const {
42+
data
43+
} = res;
44+
const currentSeconds = Date.now() / 1000;
45+
46+
bru.setEnvVar('edFiCertToken', data.access_token);
47+
bru.setEnvVar('edFiCertTokenExpiration', currentSeconds + data.expires_in);
48+
49+
console.log('Token generated successfully!');
50+
}
51+
});
52+
}
53+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
vars {
2+
apiVersion: v7.1
3+
baseUrl: https://api.ed-fi.org
4+
resourceBaseUrl: {{baseUrl}}/{{apiVersion}}/api/data/v3
5+
oauthUrl: {{baseUrl}}/{{apiVersion}}/api/oauth/token
6+
edFiClientName: {{process.env.EDFI_CLIENT_NAME}}
7+
edFiClientId: {{process.env.EDFI_CLIENT_ID}}
8+
edFiClientSecret: {{process.env.EDFI_CLIENT_SECRET}}
9+
}
10+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
vars {
2+
apiVersion: v6.2
3+
baseUrl: https://certification.ed-fi.org
4+
resourceBaseUrl: {{baseUrl}}/{{apiVersion}}/api/data/v3
5+
oauthUrl: {{baseUrl}}/{{apiVersion}}/api/oauth/token
6+
edFiClientName: {{process.env.EDFI_CLIENT_NAME}}
7+
edFiClientId: {{process.env.EDFI_CLIENT_ID}}
8+
edFiClientSecret: {{process.env.EDFI_CLIENT_SECRET}}
9+
}
10+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
vars {
2+
apiVersion: v6.2
3+
baseUrl: https://certification.ed-fi.org
4+
resourceBaseUrl: {{baseUrl}}/{{apiVersion}}/api/data/v3
5+
oauthUrl: {{baseUrl}}/{{apiVersion}}/api/oauth/token
6+
edFiClientName: QA
7+
edFiClientId: dbHD7dnJt2yc
8+
edFiClientSecret: 2rA6X3CxQ88PywCZ518Z3Hla
9+
}

bruno/Tests/package-lock.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)