Skip to content
Open
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
114 changes: 114 additions & 0 deletions .github/workflows/hubspot-auth-api-receipt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Binding C item 4a (rev2): synthetic auth/API test receipt for
# packages/v1-ready/hubspot. Never contacts the live provider, never
# reads secrets, never emits authorized/live, fails closed on HUBSPOT_*
# or MATTERS_ACCESS_TOKEN.
#
# The receipt command MUST NOT be passed via a HUBSPOT_* env var
# (that prefix is on the T-C4-8 abort list). Use AML_RECEIPT_COMMAND
# or the CLI --command argument only.
name: HubSpot auth/API test receipt

on:
push:
paths:
- 'packages/v1-ready/hubspot/**'
- 'scripts/ci/**'
- '.github/workflows/hubspot-auth-api-receipt.yml'
- 'package-lock.json'
pull_request:
paths:
- 'packages/v1-ready/hubspot/**'
- 'scripts/ci/**'
- '.github/workflows/hubspot-auth-api-receipt.yml'
- 'package-lock.json'
workflow_dispatch:

permissions:
contents: read

jobs:
hubspot-auth-api-receipt:
name: Synthetic auth/API receipt
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Fail closed if provider token env is present (T-C4-8)
run: node scripts/ci/hubspot-auth-api-receipt.cjs --assert-offline-env

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
cache: npm

# Full lock-exact install. A workspace-filtered ci would miss
# @aws-sdk/client-scheduler, which @friggframework/core@2.0.0-next.89
# requires (but does not declare) when loading the HubSpot Api.
- name: Install (npm ci, lock exact)
run: npm ci --ignore-scripts

- name: Receipt script checks (T-C4-2..T-C4-10 policy)
run: node --test scripts/ci/hubspot-auth-api-receipt.test.cjs

- name: Run 4a HubSpot auth/API tests (JSON report)
id: tests
working-directory: packages/v1-ready/hubspot
continue-on-error: true
run: |
mkdir -p "${GITHUB_WORKSPACE}/artifacts"
npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit \
--json --outputFile="${GITHUB_WORKSPACE}/artifacts/jest-results.json"

- name: Write receipt and classification bytes
env:
AML_RECEIPT_COMMAND: npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit --json --outputFile=artifacts/jest-results.json

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command field in the receipt is not the command that actually ran.

  • The test step runs in packages/v1-ready/hubspot with --outputFile="${GITHUB_WORKSPACE}/artifacts/jest-results.json".
  • AML_RECEIPT_COMMAND records --outputFile=artifacts/jest-results.json, which resolves to packages/v1-ready/hubspot/artifacts/... if someone replays it.
  • The receipt exists to record the exact command and result. Replaying the recorded command writes the JSON to a different location, and the receipt does not mention the working directory. T-C4-8 regression tests also pin this string as WORKFLOW_RECEIPT_COMMAND, so the mismatch is locked in.

Fix: make the executed command and the recorded command identical, for example by running the same string from the same directory and recording the working directory in the receipt:

env:
  AML_RECEIPT_COMMAND: npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit --json --outputFile=../../../artifacts/jest-results.json

In that case, also update WORKFLOW_RECEIPT_COMMAND to match.

Suggested change
AML_RECEIPT_COMMAND: npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit --json --outputFile=artifacts/jest-results.json
AML_RECEIPT_COMMAND: npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit --json --outputFile=../../../artifacts/jest-results.json

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

run: |
node scripts/ci/hubspot-auth-api-receipt.cjs \
--jest-json "${GITHUB_WORKSPACE}/artifacts/jest-results.json" \
--out "${GITHUB_WORKSPACE}/artifacts/hubspot-auth-api-receipt.json" \
--command "${AML_RECEIPT_COMMAND}" \
--classification synthetic \
--expect-classification synthetic \
--module-ref-branch next \
--module-ref-sha 9f5e5c627a0be42cd25cf28339142891e71eba07

- name: Packet classification must byte-equal the CI artifact (T-C4-2)
run: |
python3 - <<'PY'
from pathlib import Path
artifact = Path("artifacts/classification").read_bytes()
receipt = Path("artifacts/hubspot-auth-api-receipt.json").read_text()
import json
classify = json.loads(receipt)["classification"].encode("utf-8")
if artifact != classify or artifact != b"synthetic":
raise SystemExit(f"T-C4-2 mismatch: file={artifact!r} receipt={classify!r}")
print("T-C4-2: classification bytes equal", artifact)
PY

- name: Upload receipt artifact
# Only run (and therefore only claim success) when both files exist.
# hashFiles is empty when a path is missing; if: always() alone would
# still start the step and if-no-files-found: error would fail it,
# but a skipped upload must not be reported as a successful publish.
if: ${{ always() && hashFiles('artifacts/hubspot-auth-api-receipt.json') != '' && hashFiles('artifacts/classification') != '' }}
uses: actions/upload-artifact@v4
with:
name: hubspot-auth-api-receipt
path: |
artifacts/hubspot-auth-api-receipt.json
artifacts/classification
if-no-files-found: error

- name: Fail the job if tests or receipt failed
if: always()
run: |
if [ "${{ steps.tests.outcome }}" != "success" ]; then
echo "4a tests did not succeed (outcome=${{ steps.tests.outcome }})."
exit 1
fi
if [ ! -f artifacts/hubspot-auth-api-receipt.json ] || [ ! -f artifacts/classification ]; then
echo "Receipt or classification file missing."
exit 1
fi
22 changes: 22 additions & 0 deletions packages/v1-ready/hubspot/jest.receipt.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Jest config for the Binding C item 4a receipt job.
*
* Omits the package's MongoMemoryServer globalSetup/Teardown: the 4a tests
* exercise Definition.requiredAuthMethods and Api against nock, and do not
* need a database. Full `npm test` (jest.config.js) still uses mongo and is
* excluded from this command (see the receipt exclusions).
*/
module.exports = {
testMatch: [
'<rootDir>/tests/auth-api-synthetic.test.js',
'<rootDir>/tests/definition.test.js',
'<rootDir>/tests/listContacts.test.js',
'<rootDir>/tests/oauth.test.js',
],
testPathIgnorePatterns: [
'/node_modules/',
'/tests/api.test.js',
'/tests/auther.test.js',
'/tests/extensions/',
],
};
3 changes: 2 additions & 1 deletion packages/v1-ready/hubspot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"main": "index.js",
"scripts": {
"lint:fix": "prettier --write --loglevel error . && eslint . --fix",
"test": "jest"
"test": "jest",
"test:receipt": "jest --config jest.receipt.config.js"
},
"author": "",
"license": "MIT",
Expand Down
8 changes: 7 additions & 1 deletion packages/v1-ready/hubspot/tests/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ const parsedBody = async function async(resp) {
return body;
}

describe(`${config.label} API tests`, () => {
// Live interactive OAuth suite (Authenticator.oauth2). Not part of the
// Binding C item 4a synthetic receipt. This CI job never sets
// HUBSPOT_LIVE_API and does not wire tokens or secrets. The suite is
// reported as skipped/excluded on the receipt. It is not a 4b path.
const describeLiveApi = process.env.HUBSPOT_LIVE_API === '1' ? describe : describe.skip;

describeLiveApi(`${config.label} API tests`, () => {
const apiParams = {
client_id: process.env.HUBSPOT_CLIENT_ID,
client_secret: process.env.HUBSPOT_CLIENT_SECRET,
Expand Down
Loading
Loading