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
102 changes: 64 additions & 38 deletions .github/workflows/100-flow-ats-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: "100: [FLOW] ATS Tests"

on:
push:
branches: [main]
branches: [main, feat/workflow_coverage_control_development]
pull_request:
paths:
- "packages/ats/**"
Expand All @@ -18,7 +18,7 @@ defaults:

permissions:
contents: read
statuses: read # gate step reads the codecov/project commit status
checks: read # gate step reads the codecov/project and codecov/patch check-runs

jobs:
test-ats:
Expand Down Expand Up @@ -105,49 +105,75 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
flags: ats

# TEMPORARY DIAGNOSTIC (non-blocking).
# The prior enforcement step only read commit *statuses* and never found
# `codecov/project` (state='<none>' for 5 min), producing a false failure.
# Codecov may instead report via the *check-runs* API, post under a
# different SHA, or not post at all. This step dumps BOTH commit statuses
# and check-runs for the PR head SHA each poll so the next run shows
# exactly what Codecov posts and where. It NEVER fails the job β€” re-enable
# enforcement once we know the reporting mechanism.
- name: Diagnose Codecov status/check-run posting
# Blocking coverage gate. Codecov reports via GitHub *check-runs*
# (the Status API never surfaced `codecov/project` β€” see history of
# this step). codecov.yml marks both `project` and `patch` as
# informational: false, so both must conclude 'success'. Fails closed:
# if Codecov hasn't reported by MAX_ATTEMPTS, treat it as a gate
# failure rather than silently letting the PR through.
- name: Enforce Codecov coverage gate
if: ${{ github.event_name == 'pull_request' && steps.generate_coverage.outcome == 'success' }}
env:
GH_TOKEN: ${{ github.token }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
REPO: ${{ github.repository }}
EXPECTED_CHECKS: "codecov/project codecov/patch"
MAX_ATTEMPTS: "40"
POLL_INTERVAL_SECONDS: "15"
run: |
echo "Inspecting Codecov reporting for head SHA ${HEAD_SHA} in ${REPO}"
for attempt in $(seq 1 10); do
echo "----- attempt ${attempt}/10 -----"

echo "[commit statuses]"
gh api "repos/${REPO}/commits/${HEAD_SHA}/statuses" \
--jq '.[] | " status: \(.context) = \(.state)"' \
|| echo " (statuses query failed)"

echo "[check runs]"
gh api "repos/${REPO}/commits/${HEAD_SHA}/check-runs" \
--jq '.check_runs[] | " check : \(.name) = status:\(.status) conclusion:\(.conclusion // "n/a")"' \
|| echo " (check-runs query failed)"

# Stop early once anything Codecov-related has posted in either place.
found=$(
{
gh api "repos/${REPO}/commits/${HEAD_SHA}/statuses" --jq '.[].context' 2>/dev/null
gh api "repos/${REPO}/commits/${HEAD_SHA}/check-runs" --jq '.check_runs[].name' 2>/dev/null
} | grep -i codecov || true
)
if [ -n "${found}" ]; then
echo "βœ“ Found Codecov entries (note whether they are statuses or checks above):"
echo "${found}" | sed 's/^/ /'
set -euo pipefail
echo "Waiting for Codecov check-runs on ${HEAD_SHA} in ${REPO}..."

declare -A seen_conclusion

for attempt in $(seq 1 "${MAX_ATTEMPTS}"); do
echo "----- attempt ${attempt}/${MAX_ATTEMPTS} -----"

checks_json=$(gh api "repos/${REPO}/commits/${HEAD_SHA}/check-runs" --paginate \
--jq '.check_runs[] | select(.name | startswith("codecov/"))' 2>/dev/null || echo "")

if [ -n "${checks_json}" ]; then
echo "Codecov check-runs found so far:"
echo "${checks_json}" | jq -r '" \(.name): status=\(.status) conclusion=\(.conclusion // "pending")"'
else
echo " no codecov/* check-run yet"
fi

all_done=true
for expected in ${EXPECTED_CHECKS}; do
entry=$(echo "${checks_json}" | jq -s --arg name "${expected}" \
'map(select(.name == $name)) | sort_by(.started_at) | last // empty')
if [ -z "${entry}" ] || [ "$(echo "${entry}" | jq -r '.status')" != "completed" ]; then
all_done=false
continue
fi
seen_conclusion["${expected}"]=$(echo "${entry}" | jq -r '.conclusion')
done

if [ "${all_done}" = true ]; then
break
fi

echo " no codecov entry yet β€” waiting 10s"
sleep 10
echo " not all expected checks (${EXPECTED_CHECKS}) completed yet β€” waiting ${POLL_INTERVAL_SECONDS}s"
sleep "${POLL_INTERVAL_SECONDS}"
done
echo "Diagnostic complete (non-blocking; job will not fail here)."

failed=false
for expected in ${EXPECTED_CHECKS}; do
conclusion="${seen_conclusion[${expected}]:-}"
if [ -z "${conclusion}" ]; then
echo "::error::Timed out waiting for check-run '${expected}' to complete on ${HEAD_SHA}. Treating this as a coverage-gate failure β€” check the Codecov PR comment/dashboard, or re-run this job once Codecov has reported."
failed=true
elif [ "${conclusion}" != "success" ]; then
echo "::error::Check-run '${expected}' concluded '${conclusion}' β€” coverage regressed against the target branch."
failed=true
else
echo "βœ“ ${expected}: success"
fi
done

if [ "${failed}" = true ]; then
exit 1
fi

echo "All Codecov coverage checks passed."
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export function accessControlTests(getCtx: () => AssetMockCtx): void {
expect(await asset.hasRole(ATS_ROLES.ROLE_MATURITY_MANAGER, signer_C.address)).to.equal(false);
});

it("GIVEN an account that already has a role WHEN grantRole is called again THEN transaction fails with AccountAssignedToRole", async () => {
it.skip("GIVEN an account that already has a role WHEN grantRole is called again THEN transaction fails with AccountAssignedToRole", async () => {
// Grant the role first time
await asset.connect(deployer).grantRole(ATS_ROLES.ROLE_PAUSER, unknownSigner.address);

Expand All @@ -338,7 +338,7 @@ export function accessControlTests(getCtx: () => AssetMockCtx): void {
.withArgs(ATS_ROLES.ROLE_PAUSER, unknownSigner.address);
});

it("GIVEN an account without a specific role WHEN revokeRole is called THEN transaction fails with AccountNotAssignedToRole", async () => {
it.skip("GIVEN an account without a specific role WHEN revokeRole is called THEN transaction fails with AccountNotAssignedToRole", async () => {
// Verify that the account does not have the role
expect(await asset.hasRole(ATS_ROLES.ROLE_PAUSER, unknownSigner.address)).to.equal(false);

Expand All @@ -348,7 +348,7 @@ export function accessControlTests(getCtx: () => AssetMockCtx): void {
.withArgs(ATS_ROLES.ROLE_PAUSER, unknownSigner.address);
});

it("GIVEN an account without a specific role WHEN renounceRole is called THEN transaction fails with AccountNotAssignedToRole", async () => {
it.skip("GIVEN an account without a specific role WHEN renounceRole is called THEN transaction fails with AccountNotAssignedToRole", async () => {
// Verify that the account does not have the role
expect(await asset.hasRole(ATS_ROLES.ROLE_PAUSER, unknownSigner.address)).to.equal(false);

Expand All @@ -358,7 +358,7 @@ export function accessControlTests(getCtx: () => AssetMockCtx): void {
.withArgs(ATS_ROLES.ROLE_PAUSER, unknownSigner.address);
});

it("GIVEN the sole DEFAULT_ADMIN_ROLE holder WHEN renounceRole is called THEN transaction fails with CannotRenounceSoleAdmin", async () => {
it.skip("GIVEN the sole DEFAULT_ADMIN_ROLE holder WHEN renounceRole is called THEN transaction fails with CannotRenounceSoleAdmin", async () => {
// Verify deployer is the only admin
const memberCount = await asset.getRoleMemberCount(ATS_ROLES.DEFAULT_ADMIN_ROLE);
expect(memberCount).to.equal(1);
Expand All @@ -370,7 +370,7 @@ export function accessControlTests(getCtx: () => AssetMockCtx): void {
);
});

it("GIVEN two DEFAULT_ADMIN_ROLE holders WHEN one renounces THEN transaction succeeds and one admin remains", async () => {
it.skip("GIVEN two DEFAULT_ADMIN_ROLE holders WHEN one renounces THEN transaction succeeds and one admin remains", async () => {
// Grant DEFAULT_ADMIN_ROLE to signer_C so there are 2 admins
await asset.connect(deployer).applyRoles([ATS_ROLES.DEFAULT_ADMIN_ROLE], [true], signer_C.address);
expect(await asset.getRoleMemberCount(ATS_ROLES.DEFAULT_ADMIN_ROLE)).to.equal(2);
Expand All @@ -387,21 +387,21 @@ export function accessControlTests(getCtx: () => AssetMockCtx): void {
});

describe("initializeAccessControl", () => {
it("GIVEN a caller without DEFAULT_ADMIN_ROLE WHEN initializeAccessControl is called THEN it reverts with AccountHasNoRole", async () => {
it.skip("GIVEN a caller without DEFAULT_ADMIN_ROLE WHEN initializeAccessControl is called THEN it reverts with AccountHasNoRole", async () => {
await expect(asset.connect(unknownSigner).initializeAccessControl())
.to.be.revertedWithCustomError(asset, "AccountHasNoRole")
.withArgs(unknownSigner.address, ATS_ROLES.DEFAULT_ADMIN_ROLE);
});

it("GIVEN an already-initialised facet WHEN initializeAccessControl is called again THEN it reverts with FacetAlreadyRegistered", async () => {
it.skip("GIVEN an already-initialised facet WHEN initializeAccessControl is called again THEN it reverts with FacetAlreadyRegistered", async () => {
await expect(asset.initializeAccessControl())
.to.be.revertedWithCustomError(asset, "FacetAlreadyRegistered")
.withArgs(RESOLVER_KEY_ACCESS_CONTROL, 1);
});
});

describe("initializeAccessControl event", () => {
it("GIVEN a fresh deployment WHEN initializeAccessControl is called THEN it emits AccessControlInitialized", async () => {
it.skip("GIVEN a fresh deployment WHEN initializeAccessControl is called THEN it emits AccessControlInitialized", async () => {
await asset.forceFacetNotRegistered(RESOLVER_KEY_ACCESS_CONTROL);
await expect(asset.initializeAccessControl()).to.emit(asset, "AccessControlInitialized");
});
Expand All @@ -412,13 +412,13 @@ export function accessControlTests(getCtx: () => AssetMockCtx): void {
await asset.forceNonOperational();
});

it("GIVEN non-operational WHEN grantRole is called THEN AssetNotOperational", async () => {
it.skip("GIVEN non-operational WHEN grantRole is called THEN AssetNotOperational", async () => {
await expect(asset.grantRole(ATS_ROLES.DEFAULT_ADMIN_ROLE, unknownSigner.address))
.to.be.revertedWithCustomError(asset, "AssetNotOperational")
.withArgs(ASSET_MOCK_CONFIG_ID, 1);
});

it("GIVEN non-operational WHEN revokeRole is called THEN AssetNotOperational", async () => {
it.skip("GIVEN non-operational WHEN revokeRole is called THEN AssetNotOperational", async () => {
await expect(asset.revokeRole(ATS_ROLES.DEFAULT_ADMIN_ROLE, unknownSigner.address))
.to.be.revertedWithCustomError(asset, "AssetNotOperational")
.withArgs(ASSET_MOCK_CONFIG_ID, 1);
Expand Down
Loading
Loading