mindXtrain / tests /test_deploy_github.py
Gregory-L's picture
fork mindXtrain from GitHub (Professor-Codephreak/mindXtrain@661bd41) as the mindX-specific line
dfb775d verified
Raw
History Blame Contribute Delete
5.66 kB
"""GitHub push pure-builder tests β€” argv shape, idempotency, secret hygiene."""
from __future__ import annotations
from pathlib import Path
import pytest
from mindxtrain.deploy.github_push import (
GithubConfig,
bootstrap_steps,
missing_env,
remote_url,
required_env,
status_missing,
status_target,
write_sha_file,
)
def _cfg(**overrides: str) -> GithubConfig:
base = {
"token": "ghp_TESTTOKEN",
"repo": "professor-codephreak/mindXtrain",
"branch": "main",
"author_name": "mindXtrain bot",
"author_email": "noreply@pythai.net",
}
base.update(overrides)
return GithubConfig(**base) # type: ignore[arg-type]
def test_required_env_lists_token_and_repo() -> None:
assert "GITHUB_TOKEN" in required_env()
assert "GITHUB_REPO" in required_env()
def test_missing_env_returns_unset_keys() -> None:
miss = missing_env({"GITHUB_TOKEN": "x"})
assert "GITHUB_REPO" in miss
assert "GITHUB_TOKEN" not in miss
full = missing_env({"GITHUB_TOKEN": "x", "GITHUB_REPO": "o/r"})
assert full == []
def test_status_target_uses_repo_env() -> None:
assert status_target({"GITHUB_REPO": "owner/repo"}) == "owner/repo"
assert status_target({}) == ""
def test_remote_url_is_https() -> None:
assert remote_url("owner/repo") == "https://github.com/owner/repo.git"
def test_bootstrap_steps_emits_all_phases() -> None:
steps = bootstrap_steps(_cfg())
labels = [s.label for s in steps]
# Order matters: probe-git β†’ git-init β†’ probe-repo β†’ gh-create β†’
# probe-remote β†’ git-remote-add β†’ git-add β†’ probe-stage β†’ git-commit β†’
# git-push β†’ head-sha.
assert labels == [
"probe-git",
"git-init",
"probe-repo",
"gh-create",
"probe-remote",
"git-remote-add",
"git-add",
"probe-stage",
"git-commit",
"git-push",
"head-sha",
]
def test_token_never_appears_in_argv() -> None:
"""Secret hygiene: $GH_TOKEN is in env, never in argv. Catches a regression
where a future refactor inlines the token into a credential URL."""
steps = bootstrap_steps(_cfg(token="ghp_SUPERSECRET"))
for step in steps:
for arg in step.cmd:
assert "ghp_SUPERSECRET" not in arg, f"token leaked into {step.label}: {arg!r}"
def test_token_is_in_env_for_push_step() -> None:
steps = bootstrap_steps(_cfg(token="ghp_SUPERSECRET"))
push = next(s for s in steps if s.label == "git-push")
assert push.env["GH_TOKEN"] == "ghp_SUPERSECRET"
assert push.env["GITHUB_TOKEN"] == "ghp_SUPERSECRET"
def test_force_with_lease_only_when_force_true() -> None:
soft = bootstrap_steps(_cfg(), force=False)
push_soft = next(s for s in soft if s.label == "git-push")
assert "--force-with-lease" not in push_soft.cmd
hard = bootstrap_steps(_cfg(), force=True)
push_hard = next(s for s in hard if s.label == "git-push")
assert "--force-with-lease" in push_hard.cmd
def test_commit_uses_dash_c_flags_to_avoid_global_gitconfig() -> None:
"""The commit step injects user.email/name via `git -c …` so we never
write to ~/.gitconfig (would surprise the user). Pin the contract."""
steps = bootstrap_steps(_cfg(author_email="ci@x.com", author_name="ci"))
commit = next(s for s in steps if s.label == "git-commit")
assert "-c" in commit.cmd
assert "user.email=ci@x.com" in commit.cmd
assert "user.name=ci" in commit.cmd
def test_push_uses_credential_helper_not_url_token() -> None:
"""The push command must pass auth via credential.helper, not by
embedding the token into the remote URL (which would land in `.git/config`
or `git reflog`)."""
steps = bootstrap_steps(_cfg(token="ghp_SUPERSECRET"))
push = next(s for s in steps if s.label == "git-push")
# No URL with embedded token.
assert not any("@github.com" in arg and "x-access-token" in arg for arg in push.cmd)
# Yes credential.helper inline.
helper = [arg for arg in push.cmd if "credential.helper=" in arg]
assert helper, "git-push step is missing the credential.helper inline config"
def test_predicate_wiring_for_conditional_steps() -> None:
steps = {s.label: s for s in bootstrap_steps(_cfg())}
assert steps["git-init"].predicate_step == "probe-git"
assert steps["gh-create"].predicate_step == "probe-repo"
assert steps["gh-create"].predicate_rc_in == (1,)
assert steps["git-commit"].predicate_step == "probe-stage"
assert steps["git-commit"].predicate_rc_in == (1,)
# Probes themselves must allow_failure so a non-zero rc doesn't kill the pipeline.
for label in ("probe-git", "probe-repo", "probe-remote", "probe-stage"):
assert steps[label].allow_failure, f"{label} must allow failure"
def test_capture_stdout_on_head_sha_step() -> None:
steps = bootstrap_steps(_cfg())
sha = next(s for s in steps if s.label == "head-sha")
assert sha.capture_stdout, "head-sha must capture stdout for manifest.git_sha"
def test_write_sha_file_persists_to_disk(tmp_path: Path) -> None:
target = write_sha_file(tmp_path, "abc123\n")
assert target == tmp_path / "git_sha.txt"
assert target.read_text().strip() == "abc123"
def test_status_missing_includes_missing_binaries(monkeypatch: pytest.MonkeyPatch) -> None:
"""If gh is not on PATH, status_missing surfaces it so the UI can disable
the button + show 'install gh'."""
monkeypatch.setattr("shutil.which", lambda _b: None)
miss = status_missing({"GITHUB_TOKEN": "x", "GITHUB_REPO": "o/r"})
assert "git" in miss
assert "gh" in miss