Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/actions/schema-canary-browser/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Schema canary browser stage
description: Opens each employment the canary kept in the example app and re-submits its contract details in Chromium.

inputs:
employments-file:
description: File written by npm run schema-canary -- --keep-submitted
required: true
client-id:
required: true
client-secret:
required: true
refresh-token:
required: true

runs:
using: composite
steps:
- name: Build the library
shell: bash
run: npm run build

- name: Install example/ dependencies
shell: bash
working-directory: ./example
run: npm ci --include=optional --ignore-scripts

- name: Get Playwright version
id: playwright-version
shell: bash
working-directory: ./example
run: echo "version=$(node -p "require('./package.json').devDependencies['@playwright/test']")" >> "$GITHUB_OUTPUT"

- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}

- name: Install Playwright system dependencies
shell: bash
working-directory: ./example
run: |
for i in 1 2 3; do
if npx playwright install-deps chromium; then exit 0; fi
sleep $((i * 10))
done
exit 1

- name: Install Chromium
if: steps.playwright-cache.outputs.cache-hit != 'true'
shell: bash
working-directory: ./example
run: |
for i in 1 2 3; do
if npx playwright install chromium; then exit 0; fi
sleep $((i * 10))
done
exit 1

- name: Start the example app against sandbox
shell: bash
working-directory: ./example
env:
PORT: '3001'
VITE_REMOTE_GATEWAY: sandbox
VITE_CLIENT_ID: ${{ inputs.client-id }}
VITE_CLIENT_SECRET: ${{ inputs.client-secret }}
VITE_REFRESH_TOKEN: ${{ inputs.refresh-token }}
run: |
nohup npm run dev:env > "$RUNNER_TEMP/example-app.log" 2>&1 &
for _ in $(seq 1 60); do
if curl -sf -o /dev/null http://localhost:3001/; then exit 0; fi
sleep 2
done
cat "$RUNNER_TEMP/example-app.log"
exit 1

- name: Re-submit contract details in the browser
shell: bash
working-directory: ./example
env:
PORT: '3001'
SCHEMA_CANARY_EMPLOYMENTS: ${{ inputs.employments-file }}
run: npx playwright test -c playwright.canary.config.ts

- name: Upload the Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: schema-canary-browser-report
path: example/playwright-report/
retention-days: 14
99 changes: 92 additions & 7 deletions .github/workflows/schema-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
workflow_dispatch:
pull_request:
paths:
- 'example/src/flows/Onboarding/jsonSchemaVersions.ts'
- 'example/src/flows/Onboarding/**'
- 'package.json'
- 'package-lock.json'
- 'src/common/createHeadlessForm.tsx'
Expand All @@ -17,6 +17,11 @@ on:
- 'scripts/fill-schema.ts'
- 'scripts/schema-canary.ts'
- 'scripts/schema-canary/**'
- 'scripts/schema-canary-archive.ts'
- 'scripts/schema-canary-affected.ts'
- 'example/e2e/canary/**'
- 'example/playwright.canary.config.ts'
- '.github/actions/schema-canary-browser/**'
Comment thread
gabrielseco marked this conversation as resolved.
Resolved
- '.github/workflows/schema-canary.yml'

jobs:
Expand Down Expand Up @@ -51,7 +56,36 @@ jobs:
VITE_CLIENT_ID: ${{ secrets.SCHEMA_CANARY_CLIENT_ID }}
VITE_CLIENT_SECRET: ${{ secrets.SCHEMA_CANARY_CLIENT_SECRET }}
VITE_REFRESH_TOKEN: ${{ secrets.SCHEMA_CANARY_REFRESH_TOKEN }}
run: npm run schema-canary -- --write --failures-out="$RUNNER_TEMP/schema-canary-failures.md"
run: npm run schema-canary -- --write --failures-out="$RUNNER_TEMP/schema-canary-failures.md" --keep-submitted="$RUNNER_TEMP/kept-employments.json"

- name: Check which employments were kept
id: kept
if: ${{ !cancelled() }}
run: |
if [ -s "$RUNNER_TEMP/kept-employments.json" ] && [ "$(jq length "$RUNNER_TEMP/kept-employments.json")" -gt 0 ]; then
echo "any=true" >> "$GITHUB_OUTPUT"
fi

- name: Re-submit contract details in the browser
id: browser
if: ${{ !cancelled() && steps.kept.outputs.any == 'true' }}
uses: ./.github/actions/schema-canary-browser
with:
employments-file: ${{ runner.temp }}/kept-employments.json
client-id: ${{ secrets.SCHEMA_CANARY_CLIENT_ID }}
client-secret: ${{ secrets.SCHEMA_CANARY_CLIENT_SECRET }}
refresh-token: ${{ secrets.SCHEMA_CANARY_REFRESH_TOKEN }}

- name: Archive the kept employments
if: always()
env:
VITE_CLIENT_ID: ${{ secrets.SCHEMA_CANARY_CLIENT_ID }}
VITE_CLIENT_SECRET: ${{ secrets.SCHEMA_CANARY_CLIENT_SECRET }}
VITE_REFRESH_TOKEN: ${{ secrets.SCHEMA_CANARY_REFRESH_TOKEN }}
run: |
if [ -f "$RUNNER_TEMP/kept-employments.json" ]; then
npm run schema-canary:archive -- --from="$RUNNER_TEMP/kept-employments.json"
fi

- name: Count failing checks in the report
id: count
Expand All @@ -77,16 +111,21 @@ jobs:
scripts/reports/schema-canary.json

- name: Open or update the failure issue
if: failure() && steps.canary.outcome == 'failure'
if: failure() && (steps.canary.outcome == 'failure' || steps.browser.outcome == 'failure')
env:
GH_TOKEN: ${{ github.token }}
BROWSER_OUTCOME: ${{ steps.browser.outcome }}
run: |
{
echo "The nightly contract details schema canary is failing against sandbox."
echo
if [ "$BROWSER_OUTCOME" = "failure" ]; then
echo "The browser stage failed: re-submitting contract details in the example app did not match what the canary saved. The Playwright report is attached to the run."
echo
fi
if [ -s "$RUNNER_TEMP/schema-canary-failures.md" ]; then
cat "$RUNNER_TEMP/schema-canary-failures.md"
else
elif [ "$BROWSER_OUTCOME" != "failure" ]; then
echo "The canary stopped before it checked any country, for example because sandbox auth failed. The run log has the error."
fi
} > "$RUNNER_TEMP/issue-body.md"
Expand All @@ -101,7 +140,7 @@ jobs:
fi

- name: Close the failure issue
if: always() && steps.canary.outcome == 'success'
if: always() && steps.canary.outcome == 'success' && contains(fromJSON('["success", "skipped"]'), steps.browser.outcome)
env:
GH_TOKEN: ${{ github.token }}
run: |
Expand All @@ -113,13 +152,17 @@ jobs:
if: github.event_name == 'pull_request'
name: Contract Details Schema Canary (pinned, PR)
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: schema-canary-pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
fetch-depth: 0

- name: Use Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
Expand All @@ -129,9 +172,51 @@ jobs:
- name: Install dependencies
run: npm ci --include=optional --ignore-scripts

- name: Find the countries this PR affects
id: affected
env:
BASE_REF: ${{ github.base_ref }}
run: npm run -s schema-canary:affected -- --base="origin/$BASE_REF"

- name: Run schema canary (pinned and submit checks) against sandbox
if: steps.affected.outputs.countries != ''
env:
COUNTRIES: ${{ steps.affected.outputs.countries }}
VITE_CLIENT_ID: ${{ secrets.SCHEMA_CANARY_CLIENT_ID }}
VITE_CLIENT_SECRET: ${{ secrets.SCHEMA_CANARY_CLIENT_SECRET }}
VITE_REFRESH_TOKEN: ${{ secrets.SCHEMA_CANARY_REFRESH_TOKEN }}
run: npm run schema-canary -- --checks=pinned,submit
run: |
if [ "$COUNTRIES" = "all" ]; then
npm run schema-canary -- --checks=pinned,submit --keep-submitted="$RUNNER_TEMP/kept-employments.json"
else
npm run schema-canary -- --checks=pinned,submit --keep-submitted="$RUNNER_TEMP/kept-employments.json" --country="$COUNTRIES"
fi

- name: Check which employments were kept
id: kept
if: ${{ !cancelled() }}
run: |
if [ -s "$RUNNER_TEMP/kept-employments.json" ] && [ "$(jq length "$RUNNER_TEMP/kept-employments.json")" -gt 0 ]; then
echo "any=true" >> "$GITHUB_OUTPUT"
fi

- name: Re-submit contract details in the browser
id: browser
if: ${{ !cancelled() && steps.kept.outputs.any == 'true' }}
uses: ./.github/actions/schema-canary-browser
with:
employments-file: ${{ runner.temp }}/kept-employments.json
client-id: ${{ secrets.SCHEMA_CANARY_CLIENT_ID }}
client-secret: ${{ secrets.SCHEMA_CANARY_CLIENT_SECRET }}
refresh-token: ${{ secrets.SCHEMA_CANARY_REFRESH_TOKEN }}

- name: Archive the kept employments
if: always()
env:
VITE_CLIENT_ID: ${{ secrets.SCHEMA_CANARY_CLIENT_ID }}
VITE_CLIENT_SECRET: ${{ secrets.SCHEMA_CANARY_CLIENT_SECRET }}
VITE_REFRESH_TOKEN: ${{ secrets.SCHEMA_CANARY_REFRESH_TOKEN }}
run: |
if [ -f "$RUNNER_TEMP/kept-employments.json" ]; then
npm run schema-canary:archive -- --from="$RUNNER_TEMP/kept-employments.json"
fi
36 changes: 34 additions & 2 deletions example/api/get_token.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { buildGatewayURL } = require('./utils.js');
const { fetchCompanyManagerToken } = require('./jwt_auth.js');

async function fetchAccessToken() {
async function requestAccessToken() {
const {
VITE_CLIENT_ID,
VITE_CLIENT_SECRET,
Expand Down Expand Up @@ -56,7 +56,7 @@ async function fetchAccessToken() {
return { accessToken: data.access_token, expiresIn: data.expires_in };
}

async function fetchClientCredentialsAccessToken() {
async function requestClientCredentialsAccessToken() {
const { VITE_CLIENT_ID, VITE_CLIENT_SECRET, VITE_REMOTE_GATEWAY } =
process.env;

Expand Down Expand Up @@ -97,6 +97,38 @@ async function fetchClientCredentialsAccessToken() {
return { accessToken: data.access_token, expiresIn: data.expires_in };
}

const EXPIRY_MARGIN_SECONDS = 60;

function cacheUntilExpiry(requestToken) {
let cached;
return async () => {
if (cached && Date.now() < cached.refreshAt) {
const { accessToken } = await cached.token;
return {
accessToken,
expiresIn: Math.floor((cached.expiresAt - Date.now()) / 1000),
};
}
const token = requestToken();
cached = { token, refreshAt: Infinity, expiresAt: Infinity };
try {
const { accessToken, expiresIn } = await token;
const expiresAt = Date.now() + expiresIn * 1000;
cached.expiresAt = expiresAt;
cached.refreshAt = expiresAt - EXPIRY_MARGIN_SECONDS * 1000;
return { accessToken, expiresIn };
} catch (error) {
cached = undefined;
throw error;
}
};
}

const fetchAccessToken = cacheUntilExpiry(requestAccessToken);
const fetchClientCredentialsAccessToken = cacheUntilExpiry(
requestClientCredentialsAccessToken,
);

// Express route handler
async function getToken(req, res) {
const { NODE_ENV } = process.env;
Expand Down
Loading
Loading