#[test]
fn ci_workflow_runs_project_lint_and_tests() {
let ci = std::fs::read_to_string(".github/workflows/ci.yml").expect("read CI workflow");
assert!(ci.contains("name: CI"));
assert!(ci.contains("branches: [main]"));
assert!(ci.contains("pull_request:"));
assert!(ci.contains("actions/checkout@v6"));
assert!(ci.contains("dtolnay/rust-toolchain@stable"));
assert!(ci.contains("dtolnay/rust-toolchain@nightly"));
assert!(ci.contains("taiki-e/install-action@just"));
assert!(ci.contains("taiki-e/install-action@nextest"));
assert!(ci.contains("ubuntu-latest"));
assert!(ci.contains("macos-latest"));
assert!(ci.contains("windows-latest"));
assert!(ci.contains("run: just lint"));
assert!(ci.contains("run: just test-ci"));
}
#[test]
fn release_workflows_target_single_pointbreak_crate() {
let release_plan =
std::fs::read_to_string(".github/workflows/release-plan.yml").expect("read release plan");
let release = std::fs::read_to_string(".github/workflows/release.yml").expect("read release");
let release_script =
std::fs::read_to_string("scripts/run-release-plan.sh").expect("read release script");
let finalize_tag = std::fs::read_to_string("scripts/finalize-cocogitto-release-tag.sh")
.expect("read Cocogitto tag finalizer");
let finalize_tag_selftest =
std::fs::read_to_string("scripts/finalize-cocogitto-release-tag-selftest.sh")
.expect("read Cocogitto tag finalizer selftest");
let cog = std::fs::read_to_string("cog.toml").expect("read cog config");
assert!(release_plan.contains("select(.name == \"pointbreak\")"));
assert!(release_plan.contains("sort_by(.createdAt)"));
assert!(release_plan.contains("RELEASE_COG_CONFIG"));
assert!(release.contains("cargo publish -p pointbreak --locked"));
assert!(release.contains("https://crates.io/api/v1/crates/pointbreak/${VERSION}"));
assert!(release_script.contains("RELEASE_PLAN_REPO"));
assert!(release_script.contains("remote get-url origin"));
assert!(release_script.contains(
r#"just --justfile "$REPO_ROOT/Justfile" --working-directory "$REPO_ROOT" package-archive-selftest"#
));
let stale_repository = ["kevinswiber", "pointbreak"].join("/");
assert!(!release_script.contains(&stale_repository));
assert!(!release.contains("boardwalk"));
assert!(cog.contains(r#""git commit --amend -m 'chore: v{{version}}'""#));
assert!(cog.contains(r#""scripts/finalize-cocogitto-release-tag.sh v{{version}}""#));
assert!(!cog.contains(r#""git tag -m 'v{{version}}' v{{version}}""#));
assert!(!cog.contains("git tag -f"));
assert!(cog.contains(r#""git push origin HEAD:main""#));
assert!(cog.contains(r#""git push origin refs/tags/v{{version}}""#));
assert!(!cog.contains("gh workflow run release.yml"));
assert!(!cog.contains("gh workflow run release-binaries.yml"));
for required in [
"git cat-file -t \"$TAG_REF\"",
"git ls-remote --exit-code --tags origin \"$TAG_REF\"",
"git rev-parse \"${COG_TAG_COMMIT}^{tree}\"",
"git rev-parse \"${RELEASE_COMMIT}^{tree}\"",
"git rev-parse \"${COG_TAG_COMMIT}^\"",
"git rev-parse \"${RELEASE_COMMIT}^\"",
"git update-ref -d \"$TAG_REF\" \"$COG_TAG_COMMIT\"",
"git tag -s -m \"$TAG\" \"$TAG\"",
"git verify-tag \"$TAG\"",
] {
assert!(
finalize_tag.contains(required),
"tag finalizer is missing: {required}"
);
}
assert!(!finalize_tag.contains("git tag -f"));
assert!(finalize_tag_selftest.contains("finalize-cocogitto-release-tag.sh"));
assert!(finalize_tag_selftest.contains("verify-commit"));
assert!(finalize_tag_selftest.contains("verify-tag"));
assert!(finalize_tag_selftest.contains("refs/tags/v0.8.0"));
assert!(cog.contains(r#"repository = "pointbreak""#));
assert!(cog.contains(r#"owner = "withpointbreak""#));
let stale_owner = ["owner = \"", "kevinswiber", "\""].join("");
assert!(!cog.contains(&stale_owner));
}
#[test]
fn release_workflows_bind_the_reviewed_parent_and_keep_published_state_immutable() {
let release_plan =
std::fs::read_to_string(".github/workflows/release-plan.yml").expect("read release plan");
let release = std::fs::read_to_string(".github/workflows/release.yml").expect("read release");
let binaries = std::fs::read_to_string(".github/workflows/release-binaries.yml")
.expect("read release binaries");
let verify = std::fs::read_to_string(".github/workflows/verify-release.yml")
.expect("read post-publish verification workflow");
let helper =
std::fs::read_to_string("scripts/run-release-plan.sh").expect("read release helper");
let identity = std::fs::read_to_string("scripts/assert-release-identity.sh")
.expect("read release identity assertion");
let docs = std::fs::read_to_string("docs/releasing.md").expect("read release docs");
assert!(release_plan.contains("expected_source_commit:"));
assert!(release_plan.contains("required: true"));
assert!(release_plan.contains("EXPECTED_SOURCE_COMMIT"));
assert!(release_plan.contains("git rev-parse origin/main"));
assert!(release_plan.contains("git rev-parse HEAD^"));
assert!(release_plan.contains("git rev-parse \"refs/tags/${TAG}^{}\""));
assert!(release_plan.contains("Existing tag conflict"));
assert!(release_plan.contains("Existing crate conflict"));
assert!(release_plan.contains("Existing GitHub release conflict"));
assert!(
release_plan.contains(
"actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3"
)
);
assert!(release_plan.contains("client-id: ${{ secrets.RELEASE_APP_CLIENT_ID }}"));
assert!(release_plan.contains("private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}"));
assert!(release_plan.contains("permission-contents: write"));
assert!(release_plan.contains("token: ${{ steps.release-token.outputs.token }}"));
assert_eq!(release_plan.matches("Verify release bump hooks").count(), 2);
assert_eq!(
release_plan.matches("just release-bump-selftest").count(),
2
);
assert!(!release_plan.contains("RELEASE_PUSH_TOKEN"));
assert!(release_plan.contains("select(.headSha == $expected)"));
assert!(release_plan.contains("unexpected crates.io status"));
assert!(release_plan.contains("pointbreak-release-workflow/1.0"));
assert!(release_plan.contains("unexpected GitHub release status"));
assert!(docs.contains("RELEASE_APP_CLIENT_ID"));
assert!(docs.contains("RELEASE_APP_PRIVATE_KEY"));
assert!(!docs.contains("RELEASE_PUSH_TOKEN"));
assert!(helper.contains("<plan|release> <version> --expected-source <full-sha>"));
assert!(helper.contains("expected_source_commit=${EXPECTED_SOURCE_COMMIT}"));
assert!(helper.contains("git -C \"$REPO_ROOT\" rev-parse origin/main"));
for tag_only_workflow in [&release, &binaries] {
assert!(!tag_only_workflow.contains("workflow_dispatch:"));
assert!(tag_only_workflow.contains("fetch-depth: 0"));
}
assert!(release.contains("GitHub release already exists"));
assert!(release.contains("unexpected crates.io status"));
assert!(release.contains("unexpected GitHub release status"));
assert!(!release.contains("already published; skipping"));
assert!(binaries.contains("fetch-tags: true"));
assert!(binaries.contains("scripts/assert-release-identity.sh"));
assert!(binaries.contains("overwrite_files: false"));
assert!(!binaries.contains("overwrite_files: true"));
assert!(identity.contains(".build.source == \"git\""));
assert!(identity.contains(".build.commit == $commit"));
assert!(identity.contains(".build.describe == $tag"));
assert!(identity.contains(".build.dirty == false"));
for required in [
"ubuntu-latest",
"macos-latest",
"windows-latest",
"alpine",
"scripts/install.sh",
"scripts/install.ps1",
"release-verification",
"isPrerelease == $prerelease",
] {
assert!(
verify.contains(required),
"missing verification term: {required}"
);
}
}
#[test]
fn changelog_has_cocogitto_insertion_separator() {
let changelog = std::fs::read_to_string("CHANGELOG.md").expect("read changelog");
assert!(
changelog.lines().any(|line| line == "- - -"),
"Cocogitto release bumps need the default insertion separator"
);
}
#[test]
fn commit_check_workflow_reports_shore_examples() {
let commit_check =
std::fs::read_to_string(".github/workflows/commit-check.yml").expect("read commit check");
assert!(commit_check.contains("name: Commit Check"));
assert!(commit_check.contains("cog check \"${RANGE}\""));
assert!(commit_check.contains("feat: add review unit discovery"));
assert!(commit_check.contains("fix: correct input request projection"));
assert!(!commit_check.contains("boardwalk"));
}