use super::*;
#[test]
fn bad_deployment_tag_pattern_returns_clear_error() {
let mut db = Database::open_in_memory().expect("db");
let dora = DoraConfig {
deployment_tag_pattern: "[unclosed".into(),
..DoraConfig::default()
};
let err = ingest_git_tags(&mut db, &[], &dora).expect_err("bad regex");
let msg = format!("{err}");
assert!(
msg.contains("dora.deployment_tag_pattern"),
"error should name the field: {msg}"
);
}
#[test]
fn deploy_id_primary_key_makes_reingest_idempotent() {
let db = Database::open_in_memory().expect("db");
let conn = db.connection();
for _ in 0..2 {
conn.execute(
"INSERT OR IGNORE INTO fact_deployments \
(deploy_id, repo, environment, triggered_at, status, git_sha, git_tag, source) \
VALUES ('repo@v1.0.0', 'repo', 'production', \
'2025-01-01T00:00:00Z', 'success', 'sha', 'v1.0.0', 'git_tag')",
[],
)
.expect("insert");
}
let n: i64 = conn
.query_row("SELECT COUNT(*) FROM fact_deployments", [], |r| r.get(0))
.expect("count");
assert_eq!(n, 1, "INSERT OR IGNORE must dedupe on deploy_id PK");
}
#[test]
fn extract_owner_repo_from_url_handles_common_forms() {
assert_eq!(
extract_owner_repo_from_url("https://github.com/acme/widget.git"),
Some(("acme".to_string(), "widget".to_string()))
);
assert_eq!(
extract_owner_repo_from_url("https://github.com/acme/widget"),
Some(("acme".to_string(), "widget".to_string()))
);
assert_eq!(
extract_owner_repo_from_url("git@github.com:acme/widget.git"),
Some(("acme".to_string(), "widget".to_string()))
);
assert_eq!(
extract_owner_repo_from_url("ssh://git@github.com/acme/widget.git"),
Some(("acme".to_string(), "widget".to_string()))
);
assert_eq!(
extract_owner_repo_from_url("https://user@github.com/acme/widget"),
Some(("acme".to_string(), "widget".to_string()))
);
assert!(extract_owner_repo_from_url("https://gitlab.com/acme/widget").is_none());
assert!(extract_owner_repo_from_url("nonsense").is_none());
}
#[test]
fn resolve_repo_to_github_slug_prefers_explicit_org() {
let cfg = RepositoryConfig {
path: std::path::PathBuf::from("/tmp/some-dir"),
name: Some("widget".into()),
org: Some("acme".into()),
..Default::default()
};
assert_eq!(
resolve_repo_to_github_slug(&cfg),
Some(("acme".to_string(), "widget".to_string()))
);
}
#[test]
fn resolve_repo_to_github_slug_returns_none_when_unresolvable() {
let cfg = RepositoryConfig {
path: std::path::PathBuf::from("/nonexistent/path-xyz-987"),
name: None,
org: None,
..Default::default()
};
assert_eq!(resolve_repo_to_github_slug(&cfg), None);
}
#[test]
fn next_link_parses_canonical_github_header() {
let h = "<https://api.github.com/repositories/1/releases?page=2>; rel=\"next\", \
<https://api.github.com/repositories/1/releases?page=5>; rel=\"last\"";
assert_eq!(
parse_next_link_value(h).as_deref(),
Some("https://api.github.com/repositories/1/releases?page=2"),
);
let last_only = "<https://api.github.com/repositories/1/releases?page=5>; rel=\"last\"";
assert!(parse_next_link_value(last_only).is_none());
}
#[test]
fn api_release_deserializes_full_and_minimal() {
let full = r#"{
"id": 1,
"tag_name": "v1.2.3",
"target_commitish": "main",
"published_at": "2025-01-01T00:00:00Z",
"draft": false,
"prerelease": false
}"#;
let r: ApiRelease = serde_json::from_str(full).expect("parses");
assert_eq!(r.tag_name, "v1.2.3");
assert_eq!(r.target_commitish.as_deref(), Some("main"));
assert!(!r.draft && !r.prerelease);
assert!(r.published_at.is_some());
let minimal = r#"{"tag_name": "v0.1.0"}"#;
let r: ApiRelease = serde_json::from_str(minimal).expect("parses");
assert_eq!(r.tag_name, "v0.1.0");
assert!(r.target_commitish.is_none());
assert!(r.published_at.is_none());
assert!(!r.draft && !r.prerelease);
}
#[test]
fn api_workflow_run_deserializes() {
let json = r#"{
"workflow_runs": [
{
"id": 999,
"head_sha": "deadbeefcafebabe",
"head_branch": "main",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:05:00Z",
"conclusion": "success",
"name": "deploy-production",
"path": ".github/workflows/deploy-production.yml"
}
]
}"#;
let env: ApiWorkflowRunsEnvelope = serde_json::from_str(json).expect("parses");
assert_eq!(env.workflow_runs.len(), 1);
let r = &env.workflow_runs[0];
assert_eq!(r.id, 999);
assert_eq!(r.head_sha, "deadbeefcafebabe");
assert_eq!(r.conclusion.as_deref(), Some("success"));
assert_eq!(r.name.as_deref(), Some("deploy-production"));
}
#[test]
fn is_kept_run_enforces_conclusion_and_workflow_filter() {
let mut run = ApiWorkflowRun {
id: 1,
head_sha: "sha".into(),
head_branch: Some("main".into()),
created_at: None,
updated_at: None,
conclusion: Some("success".into()),
name: Some("deploy-production".into()),
path: Some(".github/workflows/deploy-production.yml".into()),
};
assert!(is_kept_run(&run, None));
assert!(is_kept_run(&run, Some("deploy-production")));
assert!(is_kept_run(&run, Some("deploy-production.yml")));
assert!(!is_kept_run(&run, Some("ci.yml")));
run.conclusion = Some("failure".into());
assert!(!is_kept_run(&run, None));
run.conclusion = None;
assert!(!is_kept_run(&run, None));
}