use std::path::PathBuf;
#[path = "workflow_job_block/support.rs"]
mod workflow_job_block_support;
use workflow_job_block_support::workflow_job_block;
const GATE_JOB: &str = "generated-output-gate";
const PINNED_POLY_SHA: &str = "5f609e9f6913a9e0e9046fc02fe76c0515a8e276";
const PINNED_POLY_VERSION: &str = "v0.23.0";
fn uses_line_pins_poly_at_sha(block: &str, sha: &str) -> bool {
const USES_PREFIX: &str = "uses: Goldziher/poly@";
block
.lines()
.any(|line| line.trim_start().starts_with(USES_PREFIX) && line.contains(sha))
}
fn with_block_pins_poly_version(block: &str, version: &str) -> bool {
let expected = format!("version: {version}");
block
.lines()
.any(|line| !line.trim_start().starts_with('#') && line.trim() == expected)
}
fn run_step_checks_hidden_subcommand(block: &str) -> bool {
const NEEDLE: &str = "poly hooks check --added-large-files --help";
block
.lines()
.any(|line| !line.trim_start().starts_with('#') && line.contains(NEEDLE))
}
#[test]
fn ci_workflow_pins_poly_in_the_generated_output_gate() {
let workflow_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".github/workflows/ci.yml");
let workflow = std::fs::read_to_string(&workflow_path)
.unwrap_or_else(|error| panic!("read {}: {error}", workflow_path.display()));
let block = workflow_job_block(&workflow, GATE_JOB)
.unwrap_or_else(|| panic!("{} has no `{GATE_JOB}` job", workflow_path.display()));
assert!(
uses_line_pins_poly_at_sha(&block, PINNED_POLY_SHA),
"the `{GATE_JOB}` job in {} must have a `uses: Goldziher/poly@{PINNED_POLY_SHA}` step \
(the commit `{PINNED_POLY_VERSION}` resolved to), not just prose mentioning the tag or \
SHA -- a mutable `@v0` tag pin, or a step deleted outright, would not satisfy this:\n\
--- job block as parsed ---\n{block}",
workflow_path.display()
);
assert!(
with_block_pins_poly_version(&block, PINNED_POLY_VERSION),
"the `{GATE_JOB}` job in {} must have a `with: version: {PINNED_POLY_VERSION}` step \
input, not just prose mentioning the version -- poly.toml's added-large-files hook \
depends on poly's hidden `hooks check` subcommand, so an unpinned or `latest` install \
can silently break that guard on a poly upgrade:\n\
--- job block as parsed ---\n{block}",
workflow_path.display()
);
assert!(
run_step_checks_hidden_subcommand(&block),
"the `{GATE_JOB}` job in {} must actually run `poly hooks check --added-large-files \
--help`, not just describe it in a comment -- a poly release that renames/removes the \
hidden subcommand must fail this CI job, not fail silently:\n\
--- job block as parsed ---\n{block}",
workflow_path.display()
);
}
#[test]
fn uses_line_pins_poly_at_sha_rejects_comment_only_and_reverted_pins() {
let sha = "fed55c3355480f0d1c23cb6084395e66bbb1cdc8";
let pinned_uses =
" - name: Install poly\n uses: Goldziher/poly@fed55c3355480f0d1c23cb6084395e66bbb1cdc8 # v0.22.0\n";
assert!(
uses_line_pins_poly_at_sha(pinned_uses, sha),
"a real `uses:` line pinning the SHA must be accepted"
);
let uses_comment_only =
" # Goldziher/poly@v0 currently resolves to fed55c3355480f0d1c23cb6084395e66bbb1cdc8\n";
assert!(
!uses_line_pins_poly_at_sha(uses_comment_only, sha),
"prose mentioning the tag and SHA, with no `uses:` line, must not satisfy the check"
);
let uses_tag_pin = " - name: Install poly\n uses: Goldziher/poly@v0\n";
assert!(
!uses_line_pins_poly_at_sha(uses_tag_pin, sha),
"reverting to the mutable `@v0` tag must not satisfy the SHA-pin check"
);
let uses_deleted = " - name: Some other step\n run: echo hi\n";
assert!(
!uses_line_pins_poly_at_sha(uses_deleted, sha),
"deleting the `uses:` step outright must not satisfy the SHA-pin check"
);
}
#[test]
fn with_block_pins_poly_version_rejects_comment_only_and_deleted_values() {
let version = "v0.22.0";
let pinned_with = " with:\n version: v0.22.0\n";
assert!(
with_block_pins_poly_version(pinned_with, version),
"a real `with: version:` line must be accepted"
);
let with_comment_only = " # the SHA is the real supply-chain pin; `version: v0.22.0` in `with:` is kept\n";
assert!(
!with_block_pins_poly_version(with_comment_only, version),
"prose that merely mentions `version: v0.22.0` in a comment must not satisfy the check"
);
let with_deleted = " with:\n some-other-key: true\n";
assert!(
!with_block_pins_poly_version(with_deleted, version),
"deleting the `version:` input outright must not satisfy the check"
);
}
#[test]
fn run_step_checks_hidden_subcommand_rejects_comment_only_and_reflowed_prose() {
let real_run_line = " if ! poly hooks check --added-large-files --help >/dev/null 2>&1; then\n";
assert!(
run_step_checks_hidden_subcommand(real_run_line),
"a real invocation of the hidden-subcommand probe must be accepted"
);
let wrapped_comment = " # rather than a bare `poly --version`: (2) `poly hooks check\n # --added-large-files --help` must still resolve\n";
assert!(
!run_step_checks_hidden_subcommand(wrapped_comment),
"a line-wrapped comment describing the probe must not satisfy the check"
);
let reflowed_comment = " # (2) `poly hooks check --added-large-files --help` must still resolve\n";
assert!(
!run_step_checks_hidden_subcommand(reflowed_comment),
"reflowing that same comment onto one line must still not satisfy the check -- this is \
the exact vacuity this helper exists to close"
);
let run_deleted = " echo \"poly hooks check is not invoked here\"\n";
assert!(
!run_step_checks_hidden_subcommand(run_deleted),
"deleting the real invocation outright must not satisfy the check"
);
}