Single-commit clean baseline after security scrub of niche-tells, project codenames, internal jargon, and contributor-email leaks. Contents: - 100 Rust crates (_primitives/_rust/) - 37 agent manifests (_manifests/) + generated specs (_generated/) - 67 user-invocable skills (skills/) - 33 hooks (hooks/) - Composition blocks (_blocks/) - Documentation (docs/, README.md) - TS adapter packages (_ts_packages/) - Assembler (_assembler/) - Roles (_roles/) - Templates (_templates/) - Forgejo CI (.forgejo/) Author: Denis Parfionovich <info@greendragon.info> License: see LICENSE.
162 lines
5.6 KiB
Rust
162 lines
5.6 KiB
Rust
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright 2026 <author org>
|
|
//! Integration tests for `GitlabClient` and `GitlabBackend::ensure_repo`
|
|
//! against a wiremock-served GitLab API. NO live HTTP.
|
|
|
|
use kei_git_gitlab::{GitlabBackend, GitlabClient};
|
|
use kei_runtime_core::traits::git::{GitAuthKind, GitBackend, GitRemote};
|
|
use wiremock::matchers::{body_json, header, method, path};
|
|
use wiremock::{Mock, MockServer, ResponseTemplate};
|
|
|
|
#[tokio::test]
|
|
async fn project_exists_200() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path("/api/v4/projects/owner%2Frepo"))
|
|
.and(header("PRIVATE-TOKEN", "tok"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
"id": 1, "path_with_namespace": "owner/repo"
|
|
})))
|
|
.mount(&server)
|
|
.await;
|
|
let c = GitlabClient::with_url(server.uri(), "tok").unwrap();
|
|
assert!(c.project_exists("owner/repo").await.unwrap());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn project_exists_404() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path("/api/v4/projects/owner%2Frepo"))
|
|
.respond_with(ResponseTemplate::new(404))
|
|
.mount(&server)
|
|
.await;
|
|
let c = GitlabClient::with_url(server.uri(), "tok").unwrap();
|
|
assert!(!c.project_exists("owner/repo").await.unwrap());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn create_project_201() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v4/projects"))
|
|
.and(header("PRIVATE-TOKEN", "tok"))
|
|
.and(body_json(
|
|
serde_json::json!({"name": "repo", "visibility": "private"}),
|
|
))
|
|
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
|
|
"id": 42,
|
|
"path_with_namespace": "alice/repo",
|
|
"default_branch": "main"
|
|
})))
|
|
.mount(&server)
|
|
.await;
|
|
let c = GitlabClient::with_url(server.uri(), "tok").unwrap();
|
|
let info = c.create_project("repo").await.unwrap();
|
|
assert_eq!(info.id, 42);
|
|
assert_eq!(info.path_with_namespace, "alice/repo");
|
|
assert_eq!(info.default_branch.as_deref(), Some("main"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn get_branch_sha_200() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path(
|
|
"/api/v4/projects/owner%2Frepo/repository/branches/main",
|
|
))
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
"name": "main",
|
|
"commit": { "id": "deadbeefcafef00d" }
|
|
})))
|
|
.mount(&server)
|
|
.await;
|
|
let c = GitlabClient::with_url(server.uri(), "tok").unwrap();
|
|
assert_eq!(
|
|
c.get_branch_sha("owner/repo", "main").await.unwrap(),
|
|
"deadbeefcafef00d"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn get_branch_sha_404_is_not_found() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path(
|
|
"/api/v4/projects/owner%2Frepo/repository/branches/missing",
|
|
))
|
|
.respond_with(ResponseTemplate::new(404))
|
|
.mount(&server)
|
|
.await;
|
|
let c = GitlabClient::with_url(server.uri(), "tok").unwrap();
|
|
let err = c
|
|
.get_branch_sha("owner/repo", "missing")
|
|
.await
|
|
.err()
|
|
.expect("404 must surface as Err");
|
|
let msg = format!("{err}");
|
|
assert!(
|
|
msg.contains("not found"),
|
|
"expected NotFound, got: {msg}"
|
|
);
|
|
}
|
|
|
|
/// End-to-end `ensure_repo`: project absent (404) → backend creates it (201).
|
|
/// Verifies both calls are made via the API in the correct order with the
|
|
/// correct body and headers.
|
|
#[tokio::test]
|
|
async fn ensure_repo_creates_when_missing() {
|
|
let server = MockServer::start().await;
|
|
|
|
Mock::given(method("GET"))
|
|
.and(path("/api/v4/projects/alice%2Fnewproj"))
|
|
.and(header("PRIVATE-TOKEN", "tok"))
|
|
.respond_with(ResponseTemplate::new(404))
|
|
.mount(&server)
|
|
.await;
|
|
|
|
Mock::given(method("POST"))
|
|
.and(path("/api/v4/projects"))
|
|
.and(header("PRIVATE-TOKEN", "tok"))
|
|
.and(body_json(
|
|
serde_json::json!({"name": "newproj", "visibility": "private"}),
|
|
))
|
|
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
|
|
"id": 7,
|
|
"path_with_namespace": "alice/newproj"
|
|
})))
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let c = GitlabClient::with_url(server.uri(), "tok").unwrap();
|
|
let backend = GitlabBackend::new(c, None).unwrap();
|
|
let remote = GitRemote {
|
|
url: "https://gitlab.com/alice/newproj.git".into(),
|
|
branch: "main".into(),
|
|
auth_kind: GitAuthKind::Pat,
|
|
};
|
|
backend.ensure_repo(&remote).await.expect("ensure_repo");
|
|
}
|
|
|
|
/// `ensure_repo` short-circuits when project already exists (no POST).
|
|
#[tokio::test]
|
|
async fn ensure_repo_noop_when_exists() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path("/api/v4/projects/alice%2Fexisting"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
"id": 1, "path_with_namespace": "alice/existing"
|
|
})))
|
|
.mount(&server)
|
|
.await;
|
|
// No POST mock — if backend tries to create, wiremock returns the default
|
|
// 404 for unmatched requests and the test would fail at create_project.
|
|
let c = GitlabClient::with_url(server.uri(), "tok").unwrap();
|
|
let backend = GitlabBackend::new(c, None).unwrap();
|
|
let remote = GitRemote {
|
|
url: "git@gitlab.com:alice/existing.git".into(),
|
|
branch: "main".into(),
|
|
auth_kind: GitAuthKind::SshKey,
|
|
};
|
|
backend.ensure_repo(&remote).await.expect("ensure_repo");
|
|
}
|