ci: consolidate required checks behind ci-complete

This commit is contained in:
Adam Moussa 2026-09-19 15:36:40 -04:00 • committed by Cursor Agent
parent 03a0a6514d
commit 1188a4c1cb
No known key found for this signature in database
11 changed files with 161 additions and 188 deletions

View file

@ -1,35 +0,0 @@
name: Architecture and changed-file quality
on:
pull_request:
# Required by the merge queue; see ci.yml.
merge_group:
push:
branches: [main]
permissions:
contents: read
jobs:
architecture:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "8.0.x"
# CI and local run the same complete repository gate.
- name: Repository quality gate
shell: bash
env:
# A merge group carries its own base and head; github.event.before is
# empty on that event.
BASE_REF: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
HEAD_REF: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha || github.sha }}
run: bash scripts/governance-check.sh

View file

@ -1,60 +0,0 @@
name: Terraform CI
# Static checks only. Plans run in HCP Terraform as speculative VCS runs on the
# PR (shoc-backend-dev and shoc-backend-staging). Applies are HCP auto-apply
# on merge to main (dev) and on a vX.Y.Z-staging tag (staging).
on:
pull_request:
branches: [main, dev]
paths:
- "terraform/**"
- "scripts/**"
- ".github/workflows/ci-terraform.yaml"
- ".github/workflows/deploy.yaml"
- ".github/workflows/deploy-tag.yaml"
- ".github/workflows/release.yaml"
push:
branches: [main]
paths:
- "terraform/**"
- "scripts/**"
- ".github/workflows/ci-terraform.yaml"
permissions:
contents: read
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
with:
terraform_version: "1.9.8"
terraform_wrapper: false
- name: Terraform fmt
run: terraform fmt -check -recursive terraform
- name: Validate live/dev
run: |
terraform -chdir=terraform/live/dev init -backend=false
terraform -chdir=terraform/live/dev validate
- name: Validate live/staging
run: |
terraform -chdir=terraform/live/staging init -backend=false
terraform -chdir=terraform/live/staging validate
- name: Import plan guard tests
run: python3 scripts/test-terraform-import-plan-check.py
- name: Release promotion script tests
run: |
python3 scripts/test_next_release_tag.py
python3 scripts/test_require_commit_checks.py
python3 scripts/test_check_app_terraform_isolation.py

View file

@ -2,7 +2,6 @@ name: Backend CI
on: on:
pull_request: pull_request:
branches: [main, dev, staging]
# The merge queue builds main plus the queued pull requests on a temporary # The merge queue builds main plus the queued pull requests on a temporary
# branch and only counts checks that ran on the merge_group event. # branch and only counts checks that ran on the merge_group event.
merge_group: merge_group:
@ -12,14 +11,15 @@ on:
permissions: permissions:
contents: read contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs: jobs:
build-and-test: build-and-test:
name: Build and test name: Build and test
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 20 timeout-minutes: 20
concurrency:
group: backend-ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v7
@ -29,6 +29,14 @@ jobs:
with: with:
dotnet-version: "8.0.x" dotnet-version: "8.0.x"
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.props') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Restore - name: Restore
run: dotnet restore SeaHavenIndustries.sln run: dotnet restore SeaHavenIndustries.sln
@ -37,3 +45,72 @@ jobs:
- name: Test - name: Test
run: dotnet test SeaHavenIndustries.sln --no-build --configuration Release run: dotnet test SeaHavenIndustries.sln --no-build --configuration Release
architecture:
name: architecture
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: "8.0.x"
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.props') }}
restore-keys: |
nuget-${{ runner.os }}-
- name: Set up Terraform
uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
with:
terraform_version: "1.9.8"
terraform_wrapper: false
- name: Repository quality gate
shell: bash
env:
GOVERNANCE_SKIP_BUILD_TEST: "1"
# A merge group carries its own base and head; github.event.before is
# empty on that event.
BASE_REF: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
HEAD_REF: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha || github.sha }}
run: bash scripts/governance-check.sh
review:
name: review
if: github.event_name != 'push'
uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@4a6cbfd362140a68810f0f46d338026863b8e827 # v1.0.10
ci-complete:
name: ci-complete
if: always()
needs: [build-and-test, architecture, review]
runs-on: ubuntu-latest
steps:
- name: All required jobs passed
shell: bash
env:
BUILD: ${{ needs.build-and-test.result }}
ARCH: ${{ needs.architecture.result }}
REVIEW: ${{ needs.review.result }}
run: |
set -euo pipefail
if [[ "${BUILD}" != "success" || "${ARCH}" != "success" ]]; then
echo "Build and test or architecture did not succeed: build=${BUILD} architecture=${ARCH}"
exit 1
fi
# review is skipped on push to main; it must succeed on pull_request
# and merge_group.
if [[ "${REVIEW}" != "success" && "${REVIEW}" != "skipped" ]]; then
echo "review did not succeed: ${REVIEW}"
exit 1
fi

View file

@ -1,12 +0,0 @@
name: Dependency Review
on:
pull_request:
# The dependency-review action resolves the diff from merge_group.base_sha and
# head_sha itself, so the same job satisfies the required check in the merge
# queue. One job, no conditions, one check run per event.
merge_group:
permissions:
contents: read
jobs:
review:
uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@4a6cbfd362140a68810f0f46d338026863b8e827 # v1.0.10

View file

@ -31,8 +31,6 @@ on:
- "terraform/**" - "terraform/**"
- "**/*.md" - "**/*.md"
- ".github/workflows/ci.yml" - ".github/workflows/ci.yml"
- ".github/workflows/ci-terraform.yaml"
- ".github/workflows/architecture-quality.yml"
- ".github/workflows/release.yaml" - ".github/workflows/release.yaml"
- ".github/workflows/deploy-tag.yaml" - ".github/workflows/deploy-tag.yaml"

View file

@ -44,8 +44,9 @@ bash scripts/governance-check.sh
``` ```
The command restores, verifies architecture and changed-file formatting, builds The command restores, verifies architecture and changed-file formatting, builds
Release, and runs the full test suite. CI (`architecture-quality` workflow) Release, runs the full test suite, and validates Terraform. CI (`ci.yml`)
calls the same script. See `QUALITY_GATES.md`. calls the same script from the `architecture` job (skipping G4/G5) and
aggregates results as `ci-complete`. See `QUALITY_GATES.md`.
## Hard rules (deviation needs an ADR; no wildcard suppressions) ## Hard rules (deviation needs an ADR; no wildcard suppressions)

View file

@ -6,32 +6,32 @@
> rule lives in `ARCHITECTURE_AND_CODE_QUALITY.md`. > rule lives in `ARCHITECTURE_AND_CODE_QUALITY.md`.
> >
> **CI and local run the same check.** Every gate below is executed by > **CI and local run the same check.** Every gate below is executed by
> `scripts/governance-check.sh`. The `architecture-quality` workflow calls that > `scripts/governance-check.sh` locally. The `architecture` job in `ci.yml`
> script so a green run means the same thing locally and in CI. Do not add a > calls that script (skipping G4/G5, which the `Build and test` job owns). Do
> check to CI that is not also runnable locally through the script (and vice > not add a check to CI that is not also runnable locally through the script
> versa). > (and vice versa), except G4/G5 which the local script still runs.
## Gate inventory ## Gate inventory
| # | Gate | Protects (architecture §) | Local command | CI step | | # | Gate | Protects (architecture §) | Local command | CI step |
|---|------|---------------------------|---------------|---------| |---|------|---------------------------|---------------|---------|
| G1 | Restore | build integrity | `dotnet restore SeaHavenIndustries.sln` | `architecture-quality` → `governance-check.sh` | | G1 | Restore | build integrity | `dotnet restore SeaHavenIndustries.sln` | `ci.yml` `architecture` → `governance-check.sh` |
| G2 | Architecture boundary tests | §1, §3 (dependency direction) | `dotnet test Api.SeaHavenIndustries.Tests ... --filter FullyQualifiedName~ArchitectureTests --no-restore` | same script | | G2 | Architecture boundary tests | §1, §3 (dependency direction) | `dotnet test Api.SeaHavenIndustries.Tests ... --filter FullyQualifiedName~ArchitectureTests --no-restore` | same script |
| G3 | Changed-file formatting | §1 (conventions) | `dotnet format SeaHavenIndustries.sln --no-restore --verify-no-changes --include <changed .cs>` | same script | | G3 | Changed-file formatting | §1 (conventions) | `dotnet format SeaHavenIndustries.sln --no-restore --verify-no-changes --include <changed .cs>` | same script |
| G4 | Release build | compile correctness | `dotnet build SeaHavenIndustries.sln -c Release --no-restore` | `architecture-quality` and `ci` | | G4 | Release build | compile correctness | `dotnet build SeaHavenIndustries.sln -c Release --no-restore` | `ci.yml` `Build and test` (local script still runs it) |
| G5 | Full test suite | behavior | `dotnet test SeaHavenIndustries.sln -c Release --no-build` | `architecture-quality` and `ci` | | G5 | Full test suite | behavior | `dotnet test SeaHavenIndustries.sln -c Release --no-build` | `ci.yml` `Build and test` (local script still runs it) |
| G6 | Migration gates | §7 | see §"Migration gates" below | on-demand / release | | G6 | Migration gates | §7 | see §"Migration gates" below | on-demand / release |
| G7 | Cancellation forwarding | §6 | behavior tests on changed I/O paths + analyzer | review-enforced on changed paths | | G7 | Cancellation forwarding | §6 | behavior tests on changed I/O paths + analyzer | review-enforced on changed paths |
| G8 | Error disclosure | §5 | `SanitizedErrorsTests` (part of G5) | `ci` | | G8 | Error disclosure | §5 | `SanitizedErrorsTests` (part of G5) | `ci.yml` `Build and test` |
| G9 | Board-backed regression | review framework | `REVIEW_AND_PR_FRAMEWORK.md` inventory | review-enforced | | G9 | Board-backed regression | review framework | `REVIEW_AND_PR_FRAMEWORK.md` inventory | review-enforced |
| G10 | Terraform import plan safety | live infrastructure adoption | `python scripts/test-terraform-import-plan-check.py` | `architecture-quality` → `governance-check.sh` | | G10 | Terraform import plan safety | live infrastructure adoption | `python scripts/test-terraform-import-plan-check.py` | `ci.yml` `architecture` → `governance-check.sh` |
| G11 | Terraform static validation | import configuration integrity | commands below | `ci-terraform` on PRs to `main` or `dev` | | G11 | Terraform static validation | import configuration integrity | commands below | `ci.yml` `architecture` → `governance-check.sh` |
| G13 | App/Terraform isolation | separate delivery lanes | `python3 scripts/check_app_terraform_isolation.py` | `architecture-quality` → `governance-check.sh` | | G13 | App/Terraform isolation | separate delivery lanes | `python3 scripts/check_app_terraform_isolation.py` | `ci.yml` `architecture` (live classifier: PR + local) |
## How to run locally ## How to run locally
```bash ```bash
# Complete local repository gate (mirrors the architecture-quality workflow): # Complete local repository gate (G1–G5, G10, G11, G13):
bash scripts/governance-check.sh bash scripts/governance-check.sh
# By default it compares against the default branch for changed-file formatting. # By default it compares against the default branch for changed-file formatting.
@ -51,8 +51,11 @@ The script:
6. verifies that the Terraform plan guard rejects create, delete, replacement, 6. verifies that the Terraform plan guard rejects create, delete, replacement,
unmanaged resource types, and updates not allowlisted by exact address (G10). unmanaged resource types, and updates not allowlisted by exact address (G10).
7. runs the release-tag, commit-check, and isolation unit tests. 7. runs the release-tag, commit-check, and isolation unit tests.
8. rejects a diff that contains both `terraform/` and deployable application 8. runs Terraform fmt/validate for `live/dev` and `live/staging` (G11).
files (G13). 9. rejects a diff that contains both `terraform/` and deployable application
files (G13). Live G13 skips on `merge_group` and `push`. The architecture
job in CI sets `GOVERNANCE_SKIP_BUILD_TEST=1` so steps 4–5 run only locally
and in the Build and test job.
G10 permits only exact approved resource address/type pairs for the G10 permits only exact approved resource address/type pairs for the
environment-owned boundary: Elastic environment-owned boundary: Elastic
@ -65,9 +68,10 @@ Controlled mode requires one
also requires `--environment dev` or `--environment staging`; an empty or also requires `--environment dev` or `--environment staging`; an empty or
incomplete environment plan fails. incomplete environment plan fails.
G11 runs `terraform fmt -check -recursive terraform`, `terraform init -backend=false`, G11 runs `terraform fmt -check -recursive terraform`, `terraform init -backend=false
and `terraform validate` for both `live/dev` and `live/staging` on every PR to -input=false -lockfile=readonly`, and `terraform validate` for both `live/dev`
`main` or `dev`. Org-baseline CloudFormation owns the HCP role substrate, and Terraform and `live/staging` from `governance-check.sh` on every CI event, including
merge groups. Org-baseline CloudFormation owns the HCP role substrate, and Terraform
owns the environment GitHub deploy roles, so no backend CDK or bootstrap root owns the environment GitHub deploy roles, so no backend CDK or bootstrap root
remains in the matrix. HCP plan/apply roles stay in org-baseline; this remains in the matrix. HCP plan/apply roles stay in org-baseline; this
repository never manages `hcptf-*` roles. repository never manages `hcptf-*` roles.
@ -129,8 +133,9 @@ non-empty. A future namespace move that makes reflection return zero types will
Runtime/semantic properties (cancellation actually cancelling, a query Runtime/semantic properties (cancellation actually cancelling, a query
executing in SQL, a transaction actually committing atomically) are proven by executing in SQL, a transaction actually committing atomically) are proven by
behavior tests or provider evidence, never by reflection. behavior tests or provider evidence, never by reflection.
- A passing script proves G1–G5 ran in sequence. It does not prove the - A passing local script proves G1–G5, G10, G11, and G13 ran (G4/G5 are skipped
provider-backed or review-owned gates G6–G9. in the CI architecture job). It does not prove the provider-backed or
review-owned gates G6–G9.
## Pass / fail / skip / not-run reporting ## Pass / fail / skip / not-run reporting
@ -149,8 +154,9 @@ or not-run. Never infer a pass from silence.
## Adding or changing a gate ## Adding or changing a gate
- Any new executable check goes into `scripts/governance-check.sh` **and** the - Any new executable check goes into `scripts/governance-check.sh` **and** the
`architecture-quality` workflow together (or into the full CI for G4/G5-type `architecture` job in `ci.yml` together (or into the Build and test job for
checks). One source of truth for local and CI. G4/G5-type checks). One source of truth for local and CI. The required
merge-queue check is `ci-complete`.
- A new rule documents its meaning in `ARCHITECTURE_AND_CODE_QUALITY.md` and its - A new rule documents its meaning in `ARCHITECTURE_AND_CODE_QUALITY.md` and its
command/mapping here. command/mapping here.
- Explicit, documented exceptions only — no wildcard suppressions (see - Explicit, documented exceptions only — no wildcard suppressions (see

View file

@ -78,10 +78,9 @@ one follows the G6 rules in [`QUALITY_GATES.md`](QUALITY_GATES.md).
- The PR body uses the three-section layout the template pre-fills: Summary, - The PR body uses the three-section layout the template pre-fills: Summary,
Changes and value, Ticket. The reasoning is in Changes and value, Ticket. The reasoning is in
[`REVIEW_AND_PR_FRAMEWORK.md`](REVIEW_AND_PR_FRAMEWORK.md). [`REVIEW_AND_PR_FRAMEWORK.md`](REVIEW_AND_PR_FRAMEWORK.md).
- `main` requires a code-owner review and the `Build and test`, - `main` requires a code-owner review and the `ci-complete` check. PRs merge
`architecture` and `review / dependency-review` checks. PRs merge through the through the merge queue, so a branch does not need to be updated with `main`
merge queue, so a branch does not need to be updated with `main` before it before it merges.
merges.
- Application code and `terraform/` do not change in the same PR (G13). - Application code and `terraform/` do not change in the same PR (G13).
## Deployment ## Deployment

View file

@ -2,9 +2,10 @@
# #
# governance-check.sh — local/CI parity governance gate for the Seahaven backend. # governance-check.sh — local/CI parity governance gate for the Seahaven backend.
# #
# Runs G1 restore, G2 ArchitectureTests, G3 changed-file formatting, G4 Release # Locally this runs G1–G5, G10, G11, promotion-script tests, and live G13.
# build, and G5 full tests exactly as the `architecture-quality` workflow does. # The architecture job in ci.yml sets GOVERNANCE_SKIP_BUILD_TEST=1 so G4/G5
# A green run here means the same thing locally and in that CI workflow. # run only in the Build and test job. Live G13 runs on pull_request and local
# invocations; it skips merge_group and push.
# #
# Usage: # Usage:
# bash scripts/governance-check.sh # bash scripts/governance-check.sh
@ -14,6 +15,7 @@ set -euo pipefail
SOLUTION="SeaHavenIndustries.sln" SOLUTION="SeaHavenIndustries.sln"
ARCH_TEST_PROJECT="Api.SeaHavenIndustries.Tests/Api.SeaHavenIndustries.Tests.csproj" ARCH_TEST_PROJECT="Api.SeaHavenIndustries.Tests/Api.SeaHavenIndustries.Tests.csproj"
LIVE_ROOTS=(terraform/live/dev terraform/live/staging)
log() { printf '\n\033[1m== %s ==\033[0m\n' "$1"; } log() { printf '\n\033[1m== %s ==\033[0m\n' "$1"; }
ok() { printf '\033[32mPASS\033[0m %s\n' "$1"; } ok() { printf '\033[32mPASS\033[0m %s\n' "$1"; }
@ -81,13 +83,18 @@ else
ok "G3: changed-file formatting" ok "G3: changed-file formatting"
fi fi
log "G4: Release build" if [[ "${GOVERNANCE_SKIP_BUILD_TEST:-}" == "1" ]]; then
"$DOTNET" build "$SOLUTION" -c Release --no-restore --nologo printf '\033[33mSKIP\033[0m G4: Release build (CI Build and test job owns it)\n'
ok "G4: Release build" printf '\033[33mSKIP\033[0m G5: full test suite (CI Build and test job owns it)\n'
else
log "G4: Release build"
"$DOTNET" build "$SOLUTION" -c Release --no-restore --nologo
ok "G4: Release build"
log "G5: full test suite" log "G5: full test suite"
"$DOTNET" test "$SOLUTION" -c Release --no-build --nologo "$DOTNET" test "$SOLUTION" -c Release --no-build --nologo
ok "G5: full test suite" ok "G5: full test suite"
fi
log "G10: Terraform import plan safety" log "G10: Terraform import plan safety"
python scripts/test-terraform-import-plan-check.py python scripts/test-terraform-import-plan-check.py
@ -99,10 +106,26 @@ python3 scripts/test_require_commit_checks.py
python3 scripts/test_check_app_terraform_isolation.py python3 scripts/test_check_app_terraform_isolation.py
ok "Release promotion scripts" ok "Release promotion scripts"
log "G13: application and Terraform isolation (${BASE_REF}...${HEAD_REF}, merge base ${DIFF_BASE:0:7})" log "G11: Terraform formatting and validation"
python3 scripts/check_app_terraform_isolation.py < <( if ! command -v terraform >/dev/null 2>&1; then
git diff --name-only --diff-filter=ACMR "${DIFF_BASE}" "${HEAD_REF}" bad "G11: terraform is unavailable; install Terraform or set PATH."
) exit 1
ok "G13: application and Terraform isolation" fi
terraform fmt -check -recursive terraform
for live_root in "${LIVE_ROOTS[@]}"; do
terraform -chdir="${live_root}" init -backend=false -input=false -lockfile=readonly -no-color
terraform -chdir="${live_root}" validate -no-color
done
ok "G11: Terraform formatting and validation"
if [[ -n "${GITHUB_EVENT_NAME:-}" && "${GITHUB_EVENT_NAME}" != "pull_request" ]]; then
printf '\033[33mSKIP\033[0m G13: live isolation is a pull-request property (event: %s)\n' "${GITHUB_EVENT_NAME}"
else
log "G13: application and Terraform isolation (${BASE_REF}...${HEAD_REF}, merge base ${DIFF_BASE:0:7})"
python3 scripts/check_app_terraform_isolation.py < <(
git diff --name-only --diff-filter=ACMR "${DIFF_BASE}" "${HEAD_REF}"
)
ok "G13: application and Terraform isolation"
fi
log "governance-check: all required repository gates passed" log "governance-check: all required repository gates passed"

View file

@ -12,8 +12,7 @@ import urllib.parse
import urllib.request import urllib.request
REQUIRED_CHECK_NAMES = ( REQUIRED_CHECK_NAMES = (
"Build and test", "ci-complete",
"architecture",
) )

View file

@ -12,26 +12,20 @@ class ClassifyChecksTests(unittest.TestCase):
def test_success(self) -> None: def test_success(self) -> None:
state, _ = classify_checks( state, _ = classify_checks(
[ [
{"name": "Build and test", "status": "completed", "conclusion": "success"}, {"name": "ci-complete", "status": "completed", "conclusion": "success"},
{"name": "architecture", "status": "completed", "conclusion": "success"},
] ]
) )
self.assertEqual(state, "success") self.assertEqual(state, "success")
def test_pending_missing(self) -> None: def test_pending_missing(self) -> None:
state, details = classify_checks( state, details = classify_checks([])
[
{"name": "Build and test", "status": "completed", "conclusion": "success"},
]
)
self.assertEqual(state, "pending") self.assertEqual(state, "pending")
self.assertTrue(any("architecture" in line for line in details)) self.assertTrue(any("ci-complete" in line for line in details))
def test_pending_in_progress(self) -> None: def test_pending_in_progress(self) -> None:
state, _ = classify_checks( state, _ = classify_checks(
[ [
{"name": "Build and test", "status": "in_progress", "conclusion": None}, {"name": "ci-complete", "status": "in_progress", "conclusion": None},
{"name": "architecture", "status": "completed", "conclusion": "success"},
] ]
) )
self.assertEqual(state, "pending") self.assertEqual(state, "pending")
@ -39,8 +33,7 @@ class ClassifyChecksTests(unittest.TestCase):
def test_failure(self) -> None: def test_failure(self) -> None:
state, _ = classify_checks( state, _ = classify_checks(
[ [
{"name": "Build and test", "status": "completed", "conclusion": "failure"}, {"name": "ci-complete", "status": "completed", "conclusion": "failure"},
{"name": "architecture", "status": "completed", "conclusion": "success"},
] ]
) )
self.assertEqual(state, "failure") self.assertEqual(state, "failure")
@ -49,7 +42,7 @@ class ClassifyChecksTests(unittest.TestCase):
state, _ = classify_checks( state, _ = classify_checks(
[ [
{ {
"name": "Build and test", "name": "ci-complete",
"id": 1, "id": 1,
"started_at": "2026-09-16T12:00:00Z", "started_at": "2026-09-16T12:00:00Z",
"completed_at": "2026-09-16T12:05:00Z", "completed_at": "2026-09-16T12:05:00Z",
@ -57,21 +50,13 @@ class ClassifyChecksTests(unittest.TestCase):
"conclusion": "failure", "conclusion": "failure",
}, },
{ {
"name": "Build and test", "name": "ci-complete",
"id": 3, "id": 3,
"started_at": "2026-09-16T12:10:00Z", "started_at": "2026-09-16T12:10:00Z",
"completed_at": "2026-09-16T12:12:00Z", "completed_at": "2026-09-16T12:12:00Z",
"status": "completed", "status": "completed",
"conclusion": "success", "conclusion": "success",
}, },
{
"name": "architecture",
"id": 2,
"started_at": "2026-09-16T12:00:00Z",
"completed_at": "2026-09-16T12:04:00Z",
"status": "completed",
"conclusion": "success",
},
] ]
) )
self.assertEqual(state, "success") self.assertEqual(state, "success")
@ -80,7 +65,7 @@ class ClassifyChecksTests(unittest.TestCase):
state, _ = classify_checks( state, _ = classify_checks(
[ [
{ {
"name": "architecture", "name": "ci-complete",
"id": 9, "id": 9,
"started_at": "2026-09-16T13:00:00Z", "started_at": "2026-09-16T13:00:00Z",
"completed_at": "2026-09-16T13:02:00Z", "completed_at": "2026-09-16T13:02:00Z",
@ -88,21 +73,13 @@ class ClassifyChecksTests(unittest.TestCase):
"conclusion": "failure", "conclusion": "failure",
}, },
{ {
"name": "architecture", "name": "ci-complete",
"id": 4, "id": 4,
"started_at": "2026-09-16T12:00:00Z", "started_at": "2026-09-16T12:00:00Z",
"completed_at": "2026-09-16T12:01:00Z", "completed_at": "2026-09-16T12:01:00Z",
"status": "completed", "status": "completed",
"conclusion": "success", "conclusion": "success",
}, },
{
"name": "Build and test",
"id": 5,
"started_at": "2026-09-16T12:00:00Z",
"completed_at": "2026-09-16T12:03:00Z",
"status": "completed",
"conclusion": "success",
},
] ]
) )
self.assertEqual(state, "failure") self.assertEqual(state, "failure")