Compare commits
1 commit
c11a6137d9
...
5ffe37707a
Author | SHA1 | Date | |
---|---|---|---|
|
5ffe37707a |
44 changed files with 1082 additions and 1278 deletions
1153
Cargo.lock
generated
1153
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -60,7 +60,7 @@ async-trait = "0.1"
|
|||
git-url-parse = "0.4"
|
||||
|
||||
# fs/network
|
||||
kxio = "3.0"
|
||||
kxio = { version = "1.2" }
|
||||
|
||||
# TOML parsing
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
|
@ -22,7 +22,7 @@ mod tests;
|
|||
pub struct AlertsActor {
|
||||
shout: Option<Shout>, // config for sending alerts to users
|
||||
history: History, // record of alerts sent recently (e.g. 24 hours)
|
||||
net: kxio::net::Net,
|
||||
net: kxio::network::Network,
|
||||
}
|
||||
|
||||
impl Actor for AlertsActor {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
//
|
||||
use git_next_core::{git::UserNotification, server::OutboundWebhook};
|
||||
use kxio::network::{NetRequest, NetUrl, RequestBody, ResponseType};
|
||||
use secrecy::ExposeSecret as _;
|
||||
use standardwebhooks::Webhook;
|
||||
|
||||
pub(super) async fn send_webhook(
|
||||
user_notification: &UserNotification,
|
||||
webhook_config: &OutboundWebhook,
|
||||
net: &kxio::net::Net,
|
||||
net: &kxio::network::Network,
|
||||
) {
|
||||
let Ok(webhook) =
|
||||
Webhook::from_bytes(webhook_config.secret().expose_secret().as_bytes().into())
|
||||
|
@ -21,7 +22,7 @@ async fn do_send_webhook(
|
|||
user_notification: &UserNotification,
|
||||
webhook: Webhook,
|
||||
webhook_config: &OutboundWebhook,
|
||||
net: &kxio::net::Net,
|
||||
net: &kxio::network::Network,
|
||||
) {
|
||||
let message_id = format!("msg_{}", ulid::Ulid::new());
|
||||
let timestamp = time::OffsetDateTime::now_utc();
|
||||
|
@ -34,17 +35,20 @@ async fn do_send_webhook(
|
|||
.sign(&message_id, timestamp, payload.to_string().as_ref())
|
||||
.expect("signature");
|
||||
tracing::info!(?signature, "");
|
||||
net.post(webhook_config.url())
|
||||
.body(payload.to_string())
|
||||
.header("webhook-id", message_id)
|
||||
.header("webhook-timestamp", timestamp.to_string())
|
||||
.header("webhook-signature", signature)
|
||||
.send()
|
||||
.await
|
||||
.map_or_else(
|
||||
|err| {
|
||||
tracing::warn!(?err, "sending webhook");
|
||||
},
|
||||
|_| (),
|
||||
);
|
||||
let url = webhook_config.url();
|
||||
|
||||
let net_url = NetUrl::new(url.to_string());
|
||||
let request = NetRequest::post(net_url)
|
||||
.body(RequestBody::Json(payload))
|
||||
.header("webhook-id", &message_id)
|
||||
.header("webhook-timestamp", ×tamp.to_string())
|
||||
.header("webhook-signature", &signature)
|
||||
.response_type(ResponseType::None)
|
||||
.build();
|
||||
net.post_json::<()>(request).await.map_or_else(
|
||||
|err| {
|
||||
tracing::warn!(?err, "sending webhook");
|
||||
},
|
||||
|_| (),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ use git_next_forge_forgejo::ForgeJo;
|
|||
#[cfg(feature = "github")]
|
||||
use git_next_forge_github::Github;
|
||||
|
||||
use kxio::net::Net;
|
||||
use kxio::network::Network;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Forge;
|
||||
|
||||
impl Forge {
|
||||
pub fn create(repo_details: RepoDetails, net: Net) -> Box<dyn ForgeLike> {
|
||||
pub fn create(repo_details: RepoDetails, net: Network) -> Box<dyn ForgeLike> {
|
||||
match repo_details.forge.forge_type() {
|
||||
#[cfg(feature = "forgejo")]
|
||||
git_next_core::ForgeType::ForgeJo => Box::new(ForgeJo::new(repo_details, net)),
|
||||
|
|
|
@ -11,18 +11,18 @@ use git_next_core::{
|
|||
#[cfg(feature = "forgejo")]
|
||||
#[test]
|
||||
fn test_forgejo_name() {
|
||||
let net = kxio::net::mock();
|
||||
let net = Network::new_mock();
|
||||
let repo_details = given_repo_details(git_next_core::ForgeType::ForgeJo);
|
||||
let forge = Forge::create(repo_details, net.into());
|
||||
let forge = Forge::create(repo_details, net);
|
||||
assert_eq!(forge.name(), "forgejo");
|
||||
}
|
||||
|
||||
#[cfg(feature = "github")]
|
||||
#[test]
|
||||
fn test_github_name() {
|
||||
let net = kxio::net::mock();
|
||||
let net = Network::new_mock();
|
||||
let repo_details = given_repo_details(git_next_core::ForgeType::GitHub);
|
||||
let forge = Forge::create(repo_details, net.into());
|
||||
let forge = Forge::create(repo_details, net);
|
||||
assert_eq!(forge.name(), "github");
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,12 @@ use kxio::fs::FileSystem;
|
|||
pub fn run(fs: &FileSystem) -> Result<()> {
|
||||
let pathbuf = fs.base().join(".git-next.toml");
|
||||
if fs
|
||||
.path(&pathbuf)
|
||||
.exists()
|
||||
.path_exists(&pathbuf)
|
||||
.with_context(|| format!("Checking for existing file: {pathbuf:?}"))?
|
||||
{
|
||||
eprintln!("The configuration file already exists at {pathbuf:?} - not overwritting it.",);
|
||||
} else {
|
||||
fs.file(&pathbuf)
|
||||
.write(include_str!("../default.toml"))
|
||||
fs.file_write(&pathbuf, include_str!("../default.toml"))
|
||||
.with_context(|| format!("Writing file: {pathbuf:?}"))?;
|
||||
println!("Created a default configuration file at {pathbuf:?}");
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ use std::path::PathBuf;
|
|||
|
||||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
use kxio::{fs, net};
|
||||
use kxio::{fs, network::Network};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version = clap::crate_version!(), author = clap::crate_authors!(), about = clap::crate_description!())]
|
||||
|
@ -48,7 +48,7 @@ enum Server {
|
|||
|
||||
fn main() -> Result<()> {
|
||||
let fs = fs::new(PathBuf::default());
|
||||
let net = net::new();
|
||||
let net = Network::new_real();
|
||||
let repository_factory = git::repository::factory::real();
|
||||
let commands = Commands::parse();
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
use actix::prelude::*;
|
||||
|
||||
use tracing::{debug, warn, Instrument as _};
|
||||
use tracing::{debug, Instrument as _};
|
||||
|
||||
use crate::{
|
||||
repo::{
|
||||
|
@ -26,13 +26,9 @@ impl Handler<CheckCIStatus> for RepoActor {
|
|||
self.update_tui(RepoUpdate::CheckingCI);
|
||||
// get the status - pass, fail, pending (all others map to fail, e.g. error)
|
||||
async move {
|
||||
match forge.commit_status(&next).await {
|
||||
Ok(status) => {
|
||||
debug!("got status: {status:?}");
|
||||
do_send(&addr, ReceiveCIStatus::new((next, status)), log.as_ref());
|
||||
}
|
||||
Err(err) => warn!(?err, "fetching commit status"),
|
||||
}
|
||||
let status = forge.commit_status(&next).await;
|
||||
debug!("got status: {status:?}");
|
||||
do_send(&addr, ReceiveCIStatus::new((next, status)), log.as_ref());
|
||||
}
|
||||
.in_current_span()
|
||||
.into_actor(self)
|
||||
|
|
|
@ -70,7 +70,6 @@ impl Handler<ReceiveCIStatus> for RepoActor {
|
|||
.into_actor(self)
|
||||
.wait(ctx);
|
||||
}
|
||||
Status::Error(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
server::{actor::messages::RepoUpdate, ServerActor},
|
||||
};
|
||||
use derive_more::Deref;
|
||||
use kxio::net::Net;
|
||||
use kxio::network::Network;
|
||||
use tracing::{info, instrument, warn, Instrument};
|
||||
|
||||
use git_next_core::{
|
||||
|
@ -57,7 +57,7 @@ pub struct RepoActor {
|
|||
last_dev_commit: Option<git::Commit>,
|
||||
repository_factory: Box<dyn RepositoryFactory>,
|
||||
open_repository: Option<Box<dyn OpenRepositoryLike>>,
|
||||
net: Net,
|
||||
net: Network,
|
||||
forge: Box<dyn git::ForgeLike>,
|
||||
log: Option<ActorLog>,
|
||||
notify_user_recipient: Option<Recipient<NotifyUser>>,
|
||||
|
@ -70,7 +70,7 @@ impl RepoActor {
|
|||
forge: Box<dyn git::ForgeLike>,
|
||||
listen_url: ListenUrl,
|
||||
generation: git::Generation,
|
||||
net: Net,
|
||||
net: Network,
|
||||
repository_factory: Box<dyn RepositoryFactory>,
|
||||
sleep_duration: std::time::Duration,
|
||||
notify_user_recipient: Option<Recipient<NotifyUser>>,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use git_next_core::server::ListenUrl;
|
||||
|
||||
//
|
||||
use super::*;
|
||||
|
||||
use git_next_core::server::ListenUrl;
|
||||
|
||||
pub fn has_all_valid_remote_defaults(
|
||||
open_repository: &mut MockOpenRepositoryLike,
|
||||
repo_details: &RepoDetails,
|
||||
|
@ -48,8 +48,8 @@ pub fn a_repo_alias() -> RepoAlias {
|
|||
RepoAlias::new(a_name())
|
||||
}
|
||||
|
||||
pub fn a_network() -> kxio::net::MockNet {
|
||||
kxio::net::mock()
|
||||
pub fn a_network() -> kxio::network::MockNetwork {
|
||||
kxio::network::MockNetwork::new()
|
||||
}
|
||||
|
||||
pub fn a_listen_url() -> ListenUrl {
|
||||
|
@ -152,8 +152,8 @@ pub fn a_commit_sha() -> Sha {
|
|||
Sha::new(a_name())
|
||||
}
|
||||
|
||||
pub fn a_filesystem() -> kxio::fs::TempFileSystem {
|
||||
kxio::fs::temp().expect("temp fs")
|
||||
pub fn a_filesystem() -> kxio::fs::FileSystem {
|
||||
kxio::fs::temp().unwrap_or_else(|e| panic!("{}", e))
|
||||
}
|
||||
|
||||
pub fn repo_details(fs: &kxio::fs::FileSystem) -> RepoDetails {
|
||||
|
@ -196,7 +196,7 @@ pub fn a_repo_actor(
|
|||
repo_details: RepoDetails,
|
||||
repository_factory: Box<dyn RepositoryFactory>,
|
||||
forge: Box<dyn ForgeLike>,
|
||||
net: kxio::net::Net,
|
||||
net: kxio::network::Network,
|
||||
) -> (RepoActor, ActorLog) {
|
||||
let listen_url = given::a_listen_url();
|
||||
let generation = Generation::default();
|
||||
|
|
|
@ -59,7 +59,7 @@ async fn should_open() -> TestResult {
|
|||
let _ = opened_ref.write().map(|mut l| l.push(()));
|
||||
Ok(Box::new(open_repository))
|
||||
});
|
||||
fs.dir(&repo_details.gitdir).create()?;
|
||||
fs.dir_create(&repo_details.gitdir)?;
|
||||
|
||||
//when
|
||||
let (addr, _log) = when::start_actor(repository_factory, repo_details, given::a_forge());
|
||||
|
@ -94,7 +94,7 @@ async fn when_server_has_no_repo_config_should_send_load_from_repo() -> TestResu
|
|||
|
||||
let mut repository_factory = MockRepositoryFactory::new();
|
||||
expect::open_repository(&mut repository_factory, open_repository);
|
||||
fs.dir(&repo_details.gitdir).create()?;
|
||||
fs.dir_create(&repo_details.gitdir)?;
|
||||
|
||||
//when
|
||||
let (addr, log) = when::start_actor(repository_factory, repo_details, given::a_forge());
|
||||
|
@ -123,7 +123,7 @@ async fn when_server_has_repo_config_should_send_register_webhook() -> TestResul
|
|||
|
||||
let mut repository_factory = MockRepositoryFactory::new();
|
||||
expect::open_repository(&mut repository_factory, open_repository);
|
||||
fs.dir(&repo_details.gitdir).create()?;
|
||||
fs.dir_create(&repo_details.gitdir)?;
|
||||
|
||||
//when
|
||||
let (addr, log) = when::start_actor(repository_factory, repo_details, given::a_forge());
|
||||
|
@ -156,7 +156,7 @@ async fn opened_repo_with_no_default_push_should_not_proceed() -> TestResult {
|
|||
|
||||
let mut repository_factory = MockRepositoryFactory::new();
|
||||
expect::open_repository(&mut repository_factory, open_repository);
|
||||
fs.dir(&repo_details.gitdir).create()?;
|
||||
fs.dir_create(&repo_details.gitdir)?;
|
||||
|
||||
//when
|
||||
let (addr, log) = when::start_actor(repository_factory, repo_details, given::a_forge());
|
||||
|
@ -180,7 +180,7 @@ async fn opened_repo_with_no_default_fetch_should_not_proceed() -> TestResult {
|
|||
.return_once(|| Err(git::fetch::Error::NoFetchRemoteFound));
|
||||
let mut repository_factory = MockRepositoryFactory::new();
|
||||
expect::open_repository(&mut repository_factory, open_repository);
|
||||
fs.dir(&repo_details.gitdir).create()?;
|
||||
fs.dir_create(&repo_details.gitdir)?;
|
||||
|
||||
//when
|
||||
let (addr, log) = when::start_actor(repository_factory, repo_details, given::a_forge());
|
||||
|
|
|
@ -30,7 +30,7 @@ pub fn commit_status(forge: &mut MockForgeLike, commit: Commit, status: Status)
|
|||
commit_status_forge
|
||||
.expect_commit_status()
|
||||
.with(mockall::predicate::eq(commit))
|
||||
.return_once(|_| Ok(status));
|
||||
.return_once(|_| status);
|
||||
forge
|
||||
.expect_duplicate()
|
||||
.return_once(move || Box::new(commit_status_forge));
|
||||
|
|
|
@ -20,7 +20,7 @@ use git_next_core::{
|
|||
ForgeAlias, ForgeConfig, GitDir, RepoAlias, ServerRepoConfig, StoragePathType,
|
||||
};
|
||||
|
||||
use kxio::{fs::FileSystem, net::Net};
|
||||
use kxio::{fs::FileSystem, network::Network};
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
|
@ -52,7 +52,7 @@ pub struct ServerActor {
|
|||
generation: Generation,
|
||||
webhook_actor_addr: Option<Addr<WebhookActor>>,
|
||||
fs: FileSystem,
|
||||
net: Net,
|
||||
net: Network,
|
||||
alerts: Addr<AlertsActor>,
|
||||
repository_factory: Box<dyn RepositoryFactory>,
|
||||
sleep_duration: std::time::Duration,
|
||||
|
@ -71,7 +71,7 @@ impl Actor for ServerActor {
|
|||
impl ServerActor {
|
||||
pub fn new(
|
||||
fs: FileSystem,
|
||||
net: Net,
|
||||
net: Network,
|
||||
alerts: Addr<AlertsActor>,
|
||||
repo: Box<dyn RepositoryFactory>,
|
||||
sleep_duration: std::time::Duration,
|
||||
|
@ -100,14 +100,13 @@ impl ServerActor {
|
|||
for (forge_name, _forge_config) in app_config.forges() {
|
||||
let forge_dir: PathBuf = (&forge_name).into();
|
||||
let path = server_dir.join(&forge_dir);
|
||||
let path_handle = self.fs.path(&path);
|
||||
if path_handle.exists()? {
|
||||
if !path_handle.is_dir()? {
|
||||
if self.fs.path_exists(&path)? {
|
||||
if !self.fs.path_is_dir(&path)? {
|
||||
return Err(Error::ForgeDirIsNotDirectory { path });
|
||||
}
|
||||
} else {
|
||||
tracing::info!(%forge_name, ?path_handle, "creating storage");
|
||||
self.fs.dir(&path).create_all()?;
|
||||
tracing::info!(%forge_name, ?path, "creating storage");
|
||||
self.fs.dir_create_all(&path)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +213,7 @@ impl ServerActor {
|
|||
let server_storage = app_config.storage().clone();
|
||||
let dir = server_storage.path();
|
||||
if !dir.exists() {
|
||||
if let Err(err) = self.fs.dir(dir).create() {
|
||||
if let Err(err) = self.fs.dir_create(dir) {
|
||||
error!(?err, ?dir, "Failed to create server storage");
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ use actix::prelude::*;
|
|||
use crate::alerts::{AlertsActor, History};
|
||||
|
||||
//
|
||||
pub fn a_filesystem() -> kxio::fs::TempFileSystem {
|
||||
kxio::fs::temp().expect("temp fs")
|
||||
pub fn a_filesystem() -> kxio::fs::FileSystem {
|
||||
kxio::fs::temp().unwrap_or_else(|e| panic!("{}", e))
|
||||
}
|
||||
|
||||
pub fn a_network() -> kxio::net::MockNet {
|
||||
kxio::net::mock()
|
||||
pub fn a_network() -> kxio::network::MockNetwork {
|
||||
kxio::network::MockNetwork::new()
|
||||
}
|
||||
|
||||
pub fn an_alerts_actor(net: kxio::net::Net) -> Addr<AlertsActor> {
|
||||
pub fn an_alerts_actor(net: kxio::network::Network) -> Addr<AlertsActor> {
|
||||
AlertsActor::new(None, History::new(Duration::from_millis(1)), net).start()
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ async fn when_webhook_url_has_trailing_slash_should_not_send() {
|
|||
let duration = std::time::Duration::from_millis(1);
|
||||
|
||||
// sut
|
||||
let server = ServerActor::new(fs.as_real(), net.into(), alerts, repo, duration);
|
||||
let server = ServerActor::new(fs.clone(), net.into(), alerts, repo, duration);
|
||||
|
||||
// collaborators
|
||||
let listen = Listen::new(
|
||||
|
|
|
@ -19,7 +19,7 @@ pub use actor::ServerActor;
|
|||
use git_next_core::git::RepositoryFactory;
|
||||
|
||||
use color_eyre::{eyre::Context, Result};
|
||||
use kxio::{fs::FileSystem, net::Net};
|
||||
use kxio::{fs::FileSystem, network::Network};
|
||||
use tracing::info;
|
||||
|
||||
use std::{
|
||||
|
@ -34,14 +34,12 @@ pub fn init(fs: &FileSystem) -> Result<()> {
|
|||
let file_name = "git-next-server.toml";
|
||||
let pathbuf = PathBuf::from(file_name);
|
||||
if fs
|
||||
.path(&pathbuf)
|
||||
.exists()
|
||||
.path_exists(&pathbuf)
|
||||
.with_context(|| format!("Checking for existing file: {pathbuf:?}"))?
|
||||
{
|
||||
eprintln!("The configuration file already exists at {pathbuf:?} - not overwritting it.",);
|
||||
} else {
|
||||
fs.file(&pathbuf)
|
||||
.write(include_str!("server-default.toml"))
|
||||
fs.file_write(&pathbuf, include_str!("server-default.toml"))
|
||||
.with_context(|| format!("Writing file: {pathbuf:?}"))?;
|
||||
println!("Created a default configuration file at {pathbuf:?}",);
|
||||
}
|
||||
|
@ -52,7 +50,7 @@ pub fn init(fs: &FileSystem) -> Result<()> {
|
|||
pub fn start(
|
||||
ui: bool,
|
||||
fs: FileSystem,
|
||||
net: Net,
|
||||
net: Network,
|
||||
repo: Box<dyn RepositoryFactory>,
|
||||
sleep_duration: std::time::Duration,
|
||||
) -> Result<()> {
|
||||
|
|
|
@ -6,12 +6,12 @@ mod init {
|
|||
fn should_not_update_file_if_it_exists() -> TestResult {
|
||||
let fs = kxio::fs::temp()?;
|
||||
let file = fs.base().join(".git-next.toml");
|
||||
fs.file(&file).write("contents")?;
|
||||
fs.file_write(&file, "contents")?;
|
||||
|
||||
crate::init::run(&fs)?;
|
||||
|
||||
assert_eq!(
|
||||
fs.file(&file).reader()?.to_string(),
|
||||
fs.file_read_to_string(&file)?,
|
||||
"contents",
|
||||
"The file has been changed"
|
||||
);
|
||||
|
@ -27,10 +27,10 @@ mod init {
|
|||
|
||||
let file = fs.base().join(".git-next.toml");
|
||||
|
||||
assert!(fs.path(&file).exists()?, "The file has not been created");
|
||||
assert!(fs.path_exists(&file)?, "The file has not been created");
|
||||
|
||||
assert_eq!(
|
||||
fs.file(&file).reader()?.to_string(),
|
||||
fs.file_read_to_string(&file)?,
|
||||
include_str!("../default.toml"),
|
||||
"The file does not match the default template"
|
||||
);
|
||||
|
@ -54,7 +54,7 @@ mod file_watcher {
|
|||
async fn should_not_block_calling_thread() -> TestResult {
|
||||
let fs = kxio::fs::temp()?;
|
||||
let path = fs.base().join("file");
|
||||
fs.file(&path).write("foo")?;
|
||||
fs.file_write(&path, "foo")?;
|
||||
|
||||
let listener = Listener;
|
||||
let l_addr = listener.start();
|
||||
|
|
|
@ -57,7 +57,7 @@ impl AppConfig {
|
|||
pub fn load(fs: &FileSystem) -> Result<Self> {
|
||||
let file = fs.base().join("git-next-server.toml");
|
||||
info!(?file, "");
|
||||
let str = fs.file(&file).reader()?.to_string();
|
||||
let str = fs.file_read_to_string(&file)?;
|
||||
Ok(toml::from_str(&str)?)
|
||||
}
|
||||
|
||||
|
|
|
@ -624,8 +624,10 @@ token = "{forge_token}"
|
|||
"#
|
||||
);
|
||||
println!("{file_contents}");
|
||||
fs.file(&fs.base().join("git-next-server.toml"))
|
||||
.write(file_contents.as_str())?;
|
||||
fs.file_write(
|
||||
&fs.base().join("git-next-server.toml"),
|
||||
file_contents.as_str(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,5 +3,4 @@ pub enum Status {
|
|||
Pass,
|
||||
Fail,
|
||||
Pending,
|
||||
Error(String),
|
||||
}
|
||||
|
|
|
@ -32,10 +32,7 @@ pub trait ForgeLike: std::fmt::Debug + Send + Sync {
|
|||
) -> git::forge::webhook::Result<webhook::push::Push>;
|
||||
|
||||
/// Checks the results of any (e.g. CI) status checks for the commit.
|
||||
async fn commit_status(
|
||||
&self,
|
||||
commit: &git::Commit,
|
||||
) -> git::forge::webhook::Result<git::forge::commit::Status>;
|
||||
async fn commit_status(&self, commit: &git::Commit) -> git::forge::commit::Status;
|
||||
|
||||
// Lists all the webhooks
|
||||
async fn list_webhooks(
|
||||
|
|
|
@ -4,16 +4,8 @@ pub type Result<T> = core::result::Result<T, Error>;
|
|||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("network")]
|
||||
Network(#[from] kxio::net::Error),
|
||||
Network(#[from] kxio::network::NetworkError),
|
||||
|
||||
#[error("reqwest")]
|
||||
Reqwest(#[from] kxio::net::RequestError),
|
||||
|
||||
// #[error("header")]
|
||||
// Header(#[from] http::header::InvalidHeaderValue),
|
||||
|
||||
// #[error("parse url")]
|
||||
// UrlParse(#[from] url::ParseError),
|
||||
#[error("failed to register: {0}")]
|
||||
FailedToRegister(String),
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ pub enum Error {
|
|||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("network: {0}")]
|
||||
Network(#[from] kxio::net::Error),
|
||||
Network(#[from] kxio::network::NetworkError),
|
||||
|
||||
#[error("fetch: {0}")]
|
||||
Fetch(#[from] git::fetch::Error),
|
||||
|
|
|
@ -142,8 +142,7 @@ impl RepoDetails {
|
|||
let fs = self.gitdir.as_fs();
|
||||
// load config file
|
||||
let config_filename = &self.gitdir.join("config");
|
||||
let file = fs.file(config_filename);
|
||||
let config_file = file.reader()?.to_string();
|
||||
let config_file = fs.file_read_to_string(config_filename)?;
|
||||
let mut config_lines = config_file
|
||||
.lines()
|
||||
.map(ToOwned::to_owned)
|
||||
|
@ -166,7 +165,7 @@ impl RepoDetails {
|
|||
}
|
||||
tracing::debug!(?config_lines, "updated file");
|
||||
// write config file back out
|
||||
file.write(config_lines.join("\n").as_str())?;
|
||||
fs.file_write(config_filename, config_lines.join("\n").as_str())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,12 +172,10 @@ impl TestOpenRepository {
|
|||
|
||||
fn write_origin(gitdir: &GitDir, fs: &kxio::fs::FileSystem) {
|
||||
let config_file = fs.base().join(gitdir.to_path_buf()).join(".git/config");
|
||||
let file = fs.file(&config_file);
|
||||
#[allow(clippy::expect_used)]
|
||||
let contents = file
|
||||
.reader()
|
||||
.expect("read original .git/config")
|
||||
.to_string();
|
||||
let contents = fs
|
||||
.file_read_to_string(&config_file)
|
||||
.expect("read original .git/config");
|
||||
let updated_contents = format!(
|
||||
r#"{contents}
|
||||
[remote "origin"]
|
||||
|
@ -186,7 +184,7 @@ impl TestOpenRepository {
|
|||
"#
|
||||
);
|
||||
#[allow(clippy::expect_used)]
|
||||
file.write(&updated_contents)
|
||||
fs.file_write(&config_file, &updated_contents)
|
||||
.expect("write updated .git/config");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::ops::Deref as _;
|
||||
|
||||
use crate::CommitCount;
|
||||
|
||||
//
|
||||
|
@ -11,7 +9,7 @@ fn should_return_single_item_in_commit_log_when_not_searching() -> TestResult {
|
|||
let_assert!(Ok(fs) = kxio::fs::temp());
|
||||
let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal);
|
||||
let forge_details = given::forge_details();
|
||||
let test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let_assert!(Ok(open_repository) = test_repository.open(&gitdir));
|
||||
let repo_config = &given::a_repo_config();
|
||||
let branches = repo_config.branches();
|
||||
|
@ -31,7 +29,7 @@ fn should_return_capacity_25_in_commit_log_when_searching_for_garbage() -> TestR
|
|||
let forge_details = given::forge_details().with_max_dev_commits(Some(CommitCount::from(25)));
|
||||
let_assert!(Some(max_dev_commits) = forge_details.max_dev_commits());
|
||||
assert!(**max_dev_commits >= 25);
|
||||
let test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let_assert!(Ok(open_repository) = test_repository.open(&gitdir));
|
||||
for _ in [0; 25] {
|
||||
then::create_a_commit_on_branch(&fs, &gitdir, &branch_name)?;
|
||||
|
@ -50,7 +48,7 @@ fn should_return_5_in_commit_log_when_searching_for_5th_item() -> TestResult {
|
|||
let forge_details = given::forge_details().with_max_dev_commits(Some(CommitCount::from(10)));
|
||||
let_assert!(Some(max_dev_commits) = forge_details.max_dev_commits());
|
||||
assert!(**max_dev_commits > 5);
|
||||
let test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let_assert!(
|
||||
Ok(open_repository) = test_repository.open(&gitdir),
|
||||
"open repository"
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::ops::Deref as _;
|
||||
|
||||
//
|
||||
use super::*;
|
||||
|
||||
|
@ -13,7 +11,7 @@ fn should_return_file() -> TestResult {
|
|||
let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal);
|
||||
let forge_details = given::forge_details();
|
||||
|
||||
let test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let_assert!(Ok(open_repository) = test_repository.open(&gitdir));
|
||||
then::commit_named_file_to_branch(
|
||||
&file_name,
|
||||
|
@ -37,7 +35,7 @@ fn should_error_on_missing_file() -> TestResult {
|
|||
let_assert!(Ok(fs) = kxio::fs::temp());
|
||||
let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal);
|
||||
let forge_details = given::forge_details();
|
||||
let test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let_assert!(Ok(open_repository) = test_repository.open(&gitdir));
|
||||
let repo_config = &given::a_repo_config();
|
||||
let branches = repo_config.branches();
|
||||
|
|
|
@ -23,7 +23,7 @@ fn open_where_storage_external_auth_matches() -> TestResult {
|
|||
tracing::debug!(?result, "open");
|
||||
assert!(result.is_ok());
|
||||
// verify origin in .git/config matches url
|
||||
let config = fs.file(&fs.base().join("config")).reader()?.to_string();
|
||||
let config = fs.file_read_to_string(&fs.base().join("config"))?;
|
||||
tracing::debug!(config=?config.lines().collect::<Vec<_>>(), "auth");
|
||||
assert!(config.lines().any(|line| line.contains(url)));
|
||||
Ok(())
|
||||
|
@ -60,7 +60,7 @@ fn open_where_storage_external_auth_differs_error() -> TestResult {
|
|||
));
|
||||
|
||||
// verify origin in .git/config is unchanged
|
||||
let config = fs.file(&fs.base().join("config")).reader()?.to_string();
|
||||
let config = fs.file_read_to_string(&fs.base().join("config"))?;
|
||||
tracing::debug!(config=?config.lines().collect::<Vec<_>>(), "auth");
|
||||
assert!(config.lines().any(|line| line.contains(original_url))); // the original urk
|
||||
Ok(())
|
||||
|
@ -86,7 +86,7 @@ fn open_where_storage_internal_auth_matches() -> TestResult {
|
|||
tracing::debug!(?result, "open");
|
||||
assert!(result.is_ok());
|
||||
// verify origin in .git/config matches url
|
||||
let config = fs.file(&fs.base().join("config")).reader()?.to_string();
|
||||
let config = fs.file_read_to_string(&fs.base().join("config"))?;
|
||||
tracing::debug!(config=?config.lines().collect::<Vec<_>>(), "auth");
|
||||
assert!(
|
||||
config.lines().any(|line| line.contains(url)),
|
||||
|
@ -121,7 +121,7 @@ fn open_where_storage_internal_auth_differs_update_config() -> TestResult {
|
|||
assert!(result.is_ok());
|
||||
|
||||
// verify origin in .git/config is unchanged
|
||||
let config = fs.file(&fs.base().join("config")).reader()?.to_string();
|
||||
let config = fs.file_read_to_string(&fs.base().join("config"))?;
|
||||
tracing::debug!(config=?config.lines().collect::<Vec<_>>(), "auth");
|
||||
assert!(
|
||||
config
|
||||
|
|
|
@ -306,7 +306,7 @@ pub mod given {
|
|||
webhook::Push::new(branch, sha.to_string(), message.to_string())
|
||||
}
|
||||
|
||||
pub fn a_filesystem() -> kxio::fs::TempFileSystem {
|
||||
pub fn a_filesystem() -> kxio::fs::FileSystem {
|
||||
kxio::fs::temp().unwrap_or_else(|e| panic!("{}", e))
|
||||
}
|
||||
|
||||
|
@ -337,8 +337,7 @@ pub mod given {
|
|||
let repo = gix::prepare_clone_bare(url, fs.base()).unwrap();
|
||||
repo.persist();
|
||||
// load config file
|
||||
let file = fs.file(&path.join("config"));
|
||||
let config_file = file.reader().unwrap().to_string();
|
||||
let config_file = fs.file_read_to_string(&path.join("config")).unwrap();
|
||||
// add use are origin url
|
||||
let mut config_lines = config_file.lines().collect::<Vec<_>>();
|
||||
config_lines.push(r#"[remote "origin"]"#);
|
||||
|
@ -346,7 +345,8 @@ pub mod given {
|
|||
tracing::info!(?url, %url_line, "writing");
|
||||
config_lines.push(&url_line);
|
||||
// write config file back out
|
||||
file.write(config_lines.join("\n").as_str()).unwrap();
|
||||
fs.file_write(&path.join("config"), config_lines.join("\n").as_str())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
|
@ -374,7 +374,7 @@ pub mod then {
|
|||
let pathbuf = PathBuf::from(gitdir);
|
||||
let file = fs.base().join(pathbuf).join(file_name);
|
||||
#[allow(clippy::expect_used)]
|
||||
fs.file(&file).write(contents)?;
|
||||
fs.file_write(&file, contents)?;
|
||||
// git add ${file}
|
||||
git_add_file(gitdir, &file)?;
|
||||
// git commit -m"Added ${file}"
|
||||
|
@ -396,7 +396,7 @@ pub mod then {
|
|||
let word = given::a_name();
|
||||
let pathbuf = PathBuf::from(gitdir);
|
||||
let file = fs.base().join(pathbuf).join(&word);
|
||||
fs.file(&file).write(&word)?;
|
||||
fs.file_write(&file, &word)?;
|
||||
// git add ${file}
|
||||
git_add_file(gitdir, &file)?;
|
||||
// git commit -m"Added ${file}"
|
||||
|
@ -420,9 +420,9 @@ pub mod then {
|
|||
let local_branch = gitrefs.join("heads").join(branch_name.to_string().as_str());
|
||||
let origin_heads = gitrefs.join("remotes").join("origin");
|
||||
let remote_branch = origin_heads.join(branch_name.to_string().as_str());
|
||||
let contents = fs.file(&local_branch).reader()?.to_string();
|
||||
fs.dir(&origin_heads).create_all()?;
|
||||
fs.file(&remote_branch).write(&contents)?;
|
||||
let contents = fs.file_read_to_string(&local_branch)?;
|
||||
fs.dir_create_all(&origin_heads)?;
|
||||
fs.file_write(&remote_branch, &contents)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -514,7 +514,7 @@ pub mod then {
|
|||
.join("refs")
|
||||
.join("heads")
|
||||
.join(branch_name.to_string().as_str());
|
||||
let sha = fs.file(&main_ref).reader()?.to_string();
|
||||
let sha = fs.file_read_to_string(&main_ref)?;
|
||||
Ok(git::commit::Sha::new(sha.trim().to_string()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ use crate::{
|
|||
|
||||
use assert2::let_assert;
|
||||
|
||||
use std::ops::Deref as _;
|
||||
|
||||
mod repos {
|
||||
|
||||
use super::*;
|
||||
|
@ -210,12 +208,12 @@ mod positions {
|
|||
let fs = given::a_filesystem();
|
||||
let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal);
|
||||
let forge_details = given::forge_details();
|
||||
let mut test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let mut test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let repo_config = given::a_repo_config();
|
||||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|branches, gitdir, fs| {
|
||||
// /--- 4 next
|
||||
// 0 --- 1 --- 3 main
|
||||
|
@ -261,12 +259,12 @@ mod positions {
|
|||
let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs");
|
||||
let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal);
|
||||
let forge_details = given::forge_details();
|
||||
let mut test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let mut test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let repo_config = given::a_repo_config();
|
||||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|branches, gitdir, fs| {
|
||||
// /--- 4 dev
|
||||
// 0 --- 1 --- 3 main
|
||||
|
@ -290,7 +288,7 @@ mod positions {
|
|||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|_branches, _gitdir, _fs| {
|
||||
// don't change anything
|
||||
git::fetch::Result::Ok(())
|
||||
|
@ -299,7 +297,7 @@ mod positions {
|
|||
test_repository.on_push(OnPush::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|_repo_details, branch_name, gitref, force, repo_branches, gitdir, fs| {
|
||||
assert_eq!(
|
||||
branch_name,
|
||||
|
@ -348,12 +346,12 @@ mod positions {
|
|||
let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs");
|
||||
let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal);
|
||||
let forge_details = given::forge_details();
|
||||
let mut test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let mut test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let repo_config = given::a_repo_config();
|
||||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|branches, gitdir, fs| {
|
||||
// /--- 4 dev
|
||||
// 0 --- 1 --- 3 main
|
||||
|
@ -377,7 +375,7 @@ mod positions {
|
|||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|_branches, _gitdir, _fs| {
|
||||
// don't change anything
|
||||
git::fetch::Result::Ok(())
|
||||
|
@ -386,7 +384,7 @@ mod positions {
|
|||
test_repository.on_push(OnPush::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|_repo_details, _branch_name, _gitref, _force, _repo_branches, _gitdir, _fs| {
|
||||
git::push::Result::Err(git::push::Error::Lock)
|
||||
},
|
||||
|
@ -422,12 +420,12 @@ mod positions {
|
|||
let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs");
|
||||
let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal);
|
||||
let forge_details = given::forge_details();
|
||||
let mut test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let mut test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let repo_config = given::a_repo_config();
|
||||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|branches, gitdir, fs| {
|
||||
// /--- 3 next
|
||||
// 0 --- 1 main & dev
|
||||
|
@ -448,7 +446,7 @@ mod positions {
|
|||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|_branches, _gitdir, _fs| {
|
||||
// don't change anything
|
||||
git::fetch::Result::Ok(())
|
||||
|
@ -457,7 +455,7 @@ mod positions {
|
|||
test_repository.on_push(OnPush::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|_repo_details, branch_name, gitref, force, repo_branches, gitdir, fs| {
|
||||
assert_eq!(
|
||||
branch_name,
|
||||
|
@ -508,12 +506,12 @@ mod positions {
|
|||
let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs");
|
||||
let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal);
|
||||
let forge_details = given::forge_details();
|
||||
let mut test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let mut test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let repo_config = given::a_repo_config();
|
||||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|branches, gitdir, fs| {
|
||||
// /--- 3 next
|
||||
// 0 --- 1 main
|
||||
|
@ -535,7 +533,7 @@ mod positions {
|
|||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|_branches, _gitdir, _fs| {
|
||||
// don't change anything
|
||||
git::fetch::Result::Ok(())
|
||||
|
@ -544,7 +542,7 @@ mod positions {
|
|||
test_repository.on_push(OnPush::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|_repo_details, branch_name, gitref, force, repo_branches, gitdir, fs| {
|
||||
assert_eq!(
|
||||
branch_name,
|
||||
|
@ -606,12 +604,12 @@ mod positions {
|
|||
let_assert!(Ok(fs) = kxio::fs::temp(), "temp fs");
|
||||
let gitdir = GitDir::new(fs.base().to_path_buf(), StoragePathType::Internal);
|
||||
let forge_details = given::forge_details();
|
||||
let mut test_repository = git::repository::test(fs.deref().clone(), forge_details);
|
||||
let mut test_repository = git::repository::test(fs.clone(), forge_details);
|
||||
let repo_config = given::a_repo_config();
|
||||
test_repository.on_fetch(OnFetch::new(
|
||||
repo_config.branches().clone(),
|
||||
gitdir.clone(),
|
||||
fs.deref().clone(),
|
||||
fs.clone(),
|
||||
|branches, gitdir, fs| {
|
||||
// 0 --- 1 main
|
||||
// \--- 2 next
|
||||
|
|
|
@ -13,16 +13,17 @@ use git_next_core::{
|
|||
ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
|
||||
};
|
||||
|
||||
use kxio::net::Net;
|
||||
use kxio::network::{self, Network};
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ForgeJo {
|
||||
repo_details: git::RepoDetails,
|
||||
net: Net,
|
||||
net: Network,
|
||||
}
|
||||
impl ForgeJo {
|
||||
#[must_use]
|
||||
pub const fn new(repo_details: git::RepoDetails, net: Net) -> Self {
|
||||
pub const fn new(repo_details: git::RepoDetails, net: Network) -> Self {
|
||||
Self { repo_details, net }
|
||||
}
|
||||
}
|
||||
|
@ -51,28 +52,45 @@ impl git::ForgeLike for ForgeJo {
|
|||
webhook::parse_body(body)
|
||||
}
|
||||
|
||||
async fn commit_status(&self, commit: &git::Commit) -> git::forge::webhook::Result<Status> {
|
||||
async fn commit_status(&self, commit: &git::Commit) -> Status {
|
||||
let repo_details = &self.repo_details;
|
||||
let hostname = &repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let api_token = &repo_details.forge.token();
|
||||
use secrecy::ExposeSecret;
|
||||
let token = api_token.expose_secret();
|
||||
let url = format!(
|
||||
let url = network::NetUrl::new(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/commits/{commit}/status?token={token}"
|
||||
);
|
||||
));
|
||||
|
||||
let Ok(response) = self.net.get(url).send().await else {
|
||||
return Ok(Status::Pending);
|
||||
};
|
||||
let combined_status = response.json::<CombinedStatus>().await.unwrap_or_default();
|
||||
eprintln!("combined_status: {:?}", combined_status);
|
||||
let status = match combined_status.state {
|
||||
ForgejoState::Success => Status::Pass,
|
||||
ForgejoState::Pending | ForgejoState::Blank => Status::Pending,
|
||||
ForgejoState::Failure | ForgejoState::Error => Status::Fail,
|
||||
};
|
||||
Ok(status)
|
||||
let request = network::NetRequest::new(
|
||||
network::RequestMethod::Get,
|
||||
url,
|
||||
network::NetRequestHeaders::new(),
|
||||
network::RequestBody::None,
|
||||
network::ResponseType::Json,
|
||||
None,
|
||||
network::NetRequestLogging::None,
|
||||
);
|
||||
let result = self.net.get::<CombinedStatus>(request).await;
|
||||
match result {
|
||||
Ok(response) => match response.response_body() {
|
||||
Some(status) => match status.state {
|
||||
ForgejoState::Success => Status::Pass,
|
||||
ForgejoState::Pending | ForgejoState::Blank => Status::Pending,
|
||||
ForgejoState::Failure | ForgejoState::Error => Status::Fail,
|
||||
},
|
||||
None => {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
unreachable!(); // response.response_body() is always Some when
|
||||
// request responseType::Json
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
warn!(?e, "Failed to get commit status");
|
||||
Status::Pending // assume issue is transient and allow retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_webhooks(
|
||||
|
@ -94,17 +112,16 @@ impl git::ForgeLike for ForgeJo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct CombinedStatus {
|
||||
pub state: ForgejoState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
enum ForgejoState {
|
||||
#[serde(rename = "success")]
|
||||
Success,
|
||||
#[serde(rename = "pending")]
|
||||
#[default]
|
||||
Pending,
|
||||
#[serde(rename = "failure")]
|
||||
Failure,
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
//
|
||||
#![allow(clippy::expect_used)] // used with mock net
|
||||
|
||||
use crate::ForgeJo;
|
||||
use git_next_core::{
|
||||
git::{self, forge::commit::Status, ForgeLike as _},
|
||||
server::{ListenUrl, RepoListenUrl},
|
||||
server::ListenUrl,
|
||||
BranchName, ForgeAlias, ForgeConfig, ForgeNotification, ForgeType, GitDir, Hostname, RepoAlias,
|
||||
RepoBranches, RepoPath, ServerRepoConfig, StoragePathType, WebhookAuth, WebhookId,
|
||||
};
|
||||
|
||||
use assert2::let_assert;
|
||||
use kxio::net::{MockNet, StatusCode};
|
||||
use kxio::network::{self, MockNetwork, StatusCode};
|
||||
use secrecy::ExposeSecret;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
@ -118,105 +116,77 @@ mod forgejo {
|
|||
let fs = given::a_filesystem();
|
||||
let repo_details = given::repo_details(&fs);
|
||||
let commit = given::a_commit();
|
||||
let net = given::a_network();
|
||||
given::a_commit_state("success", &net, &repo_details, &commit);
|
||||
let mut net = given::a_network();
|
||||
given::a_commit_state("success", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
assert_eq!(
|
||||
forge.commit_status(&commit).await.expect("status"),
|
||||
Status::Pass
|
||||
);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Pass);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn should_return_pending_for_pending() {
|
||||
let fs = given::a_filesystem();
|
||||
let repo_details = given::repo_details(&fs);
|
||||
let commit = given::a_commit();
|
||||
let net = given::a_network();
|
||||
given::a_commit_state("pending", &net, &repo_details, &commit);
|
||||
let mut net = given::a_network();
|
||||
given::a_commit_state("pending", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
assert_eq!(
|
||||
forge.commit_status(&commit).await.expect("status"),
|
||||
Status::Pending
|
||||
);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Pending);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn should_return_fail_for_failure() {
|
||||
let fs = given::a_filesystem();
|
||||
let repo_details = given::repo_details(&fs);
|
||||
let commit = given::a_commit();
|
||||
let net = given::a_network();
|
||||
given::a_commit_state("failure", &net, &repo_details, &commit);
|
||||
let mut net = given::a_network();
|
||||
given::a_commit_state("failure", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
assert_eq!(
|
||||
forge.commit_status(&commit).await.expect("status"),
|
||||
Status::Fail
|
||||
);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Fail);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn should_return_fail_for_error() {
|
||||
let fs = given::a_filesystem();
|
||||
let repo_details = given::repo_details(&fs);
|
||||
let commit = given::a_commit();
|
||||
let net = given::a_network();
|
||||
given::a_commit_state("error", &net, &repo_details, &commit);
|
||||
let mut net = given::a_network();
|
||||
given::a_commit_state("error", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
assert_eq!(
|
||||
forge.commit_status(&commit).await.expect("status"),
|
||||
Status::Fail
|
||||
);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Fail);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn should_return_pending_for_blank() {
|
||||
let fs = given::a_filesystem();
|
||||
let repo_details = given::repo_details(&fs);
|
||||
let commit = given::a_commit();
|
||||
let net = given::a_network();
|
||||
given::a_commit_state("", &net, &repo_details, &commit);
|
||||
let mut net = given::a_network();
|
||||
given::a_commit_state("", &mut net, &repo_details, &commit);
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
assert_eq!(
|
||||
forge.commit_status(&commit).await.expect("status"),
|
||||
Status::Pending
|
||||
);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Pending);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn should_return_pending_for_no_statuses() {
|
||||
let fs = given::a_filesystem();
|
||||
let repo_details = given::repo_details(&fs);
|
||||
let commit = given::a_commit();
|
||||
let net = given::a_network();
|
||||
net.on()
|
||||
.get(given::a_commit_status_url(&repo_details, &commit))
|
||||
.respond(StatusCode::OK)
|
||||
.body(
|
||||
json!({
|
||||
"state": "" // blank => Pending
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("mock");
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
assert_eq!(
|
||||
forge.commit_status(&commit).await.expect("status"),
|
||||
Status::Pending
|
||||
let mut net = given::a_network();
|
||||
net.add_get_response(
|
||||
&given::a_commit_status_url(&repo_details, &commit),
|
||||
StatusCode::OK,
|
||||
"",
|
||||
);
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Pending);
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn should_return_pending_for_network_error() {
|
||||
let fs = given::a_filesystem();
|
||||
let repo_details = given::repo_details(&fs);
|
||||
let commit = given::a_commit();
|
||||
let mock_net = given::a_network();
|
||||
mock_net
|
||||
.on()
|
||||
.get(given::a_commit_status_url(&repo_details, &commit))
|
||||
.respond(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body("book today")
|
||||
.expect("mock");
|
||||
let forge = given::a_forgejo_forge(&repo_details, mock_net);
|
||||
assert_eq!(
|
||||
forge.commit_status(&commit).await.expect("status"),
|
||||
Status::Pending
|
||||
let mut net = given::a_network();
|
||||
net.add_get_error(
|
||||
&given::a_commit_status_url(&repo_details, &commit),
|
||||
"boom today",
|
||||
);
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Pending);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,15 +246,13 @@ mod forgejo {
|
|||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
let net = given::a_network();
|
||||
let mut net = given::a_network();
|
||||
|
||||
net.on()
|
||||
.get(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?page=1&token={token}"
|
||||
))
|
||||
.respond(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body("error_message")
|
||||
.expect("mock");
|
||||
net.add_get_error(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?page=1&token={token}")
|
||||
.as_str(),
|
||||
"error_message",
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
|
@ -303,15 +271,16 @@ mod forgejo {
|
|||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
let webhook_id = given::a_webhook_id();
|
||||
let net = given::a_network();
|
||||
let mut net = given::a_network();
|
||||
|
||||
net.on()
|
||||
.delete(format!(
|
||||
net.add_delete_response(
|
||||
format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks/{webhook_id}?token={token}"
|
||||
))
|
||||
.respond(StatusCode::OK)
|
||||
.body("")
|
||||
.expect("mock");
|
||||
)
|
||||
.as_str(),
|
||||
StatusCode::OK,
|
||||
"",
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
|
@ -326,15 +295,15 @@ mod forgejo {
|
|||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
let webhook_id = given::a_webhook_id();
|
||||
let net = given::a_network();
|
||||
let mut net = given::a_network();
|
||||
|
||||
net.on()
|
||||
.delete(format!(
|
||||
net.add_delete_error(
|
||||
format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks/{webhook_id}?token={token}"
|
||||
))
|
||||
.respond(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body("error")
|
||||
.expect("mock");
|
||||
)
|
||||
.as_str(),
|
||||
"error",
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
|
@ -374,13 +343,11 @@ mod forgejo {
|
|||
with::get_webhooks_by_page(1, &[], &mut args);
|
||||
// register the webhook will succeed
|
||||
let webhook_id = given::a_forgejo_webhook_id();
|
||||
net.on()
|
||||
.post(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
|
||||
))
|
||||
.respond(StatusCode::OK)
|
||||
.body(json!({"id": webhook_id, "config":{}}).to_string())
|
||||
.expect("mock");
|
||||
net.add_post_response(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}").as_str(),
|
||||
StatusCode::OK,
|
||||
json!({"id": webhook_id, "config":{}}).to_string().as_str(),
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
|
@ -448,13 +415,11 @@ mod forgejo {
|
|||
with::unregister_webhook(&hooks[1], &mut args);
|
||||
// register the webhook will succeed
|
||||
let webhook_id = given::a_forgejo_webhook_id();
|
||||
net.on()
|
||||
.post(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
|
||||
))
|
||||
.respond(StatusCode::OK)
|
||||
.body(json!({"id": webhook_id, "config":{}}).to_string())
|
||||
.expect("mock");
|
||||
net.add_post_response(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}").as_str(),
|
||||
StatusCode::OK,
|
||||
json!({"id": webhook_id, "config":{}}).to_string().as_str(),
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
|
@ -489,19 +454,17 @@ mod forgejo {
|
|||
// there are no existing matching webhooks
|
||||
with::get_webhooks_by_page(1, &[], &mut args);
|
||||
// register the webhook will return empty response
|
||||
net.on()
|
||||
.post(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
|
||||
))
|
||||
.respond(StatusCode::OK)
|
||||
.body(json!({}).to_string()) // empty response)
|
||||
.expect("mock");
|
||||
net.add_post_response(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}").as_str(),
|
||||
StatusCode::OK,
|
||||
json!({}).to_string().as_str(), // empty response
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert!(
|
||||
matches!(err, git::forge::webhook::Error::NetworkResponseEmpty),
|
||||
matches!(err, git::forge::webhook::Error::FailedToRegister(_)),
|
||||
"{err:?}"
|
||||
);
|
||||
}
|
||||
|
@ -530,13 +493,10 @@ mod forgejo {
|
|||
// there are no existing matching webhooks
|
||||
with::get_webhooks_by_page(1, &[], &mut args);
|
||||
// register the webhook will return empty response
|
||||
net.on()
|
||||
.post(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
|
||||
))
|
||||
.respond(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body("error")
|
||||
.expect("mock");
|
||||
net.add_post_error(
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}").as_str(),
|
||||
"error",
|
||||
);
|
||||
|
||||
let forge = given::a_forgejo_forge(&repo_details, net);
|
||||
|
||||
|
@ -548,6 +508,7 @@ mod forgejo {
|
|||
}
|
||||
}
|
||||
mod with {
|
||||
use git_next_core::server::RepoListenUrl;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -559,31 +520,31 @@ mod forgejo {
|
|||
let hostname = args.hostname;
|
||||
let repo_path = args.repo_path;
|
||||
let token = args.token;
|
||||
args.net
|
||||
.on()
|
||||
.get(format!(
|
||||
args.net.add_get_response(
|
||||
format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?page={page}&token={token}"
|
||||
))
|
||||
.respond(StatusCode::OK)
|
||||
.body(json!(response).to_string())
|
||||
.expect("mock");
|
||||
)
|
||||
.as_str(),
|
||||
StatusCode::OK,
|
||||
json!(response).to_string().as_str(),
|
||||
);
|
||||
}
|
||||
pub fn unregister_webhook(hook1: &ReturnedWebhook, args: &mut WebhookArgs) {
|
||||
let webhook_id = hook1.id;
|
||||
let hostname = args.hostname;
|
||||
let repo_path = args.repo_path;
|
||||
let token = args.token;
|
||||
args.net
|
||||
.on()
|
||||
.delete(format!(
|
||||
args.net.add_delete_response(
|
||||
format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks/{webhook_id}?token={token}"
|
||||
))
|
||||
.respond(StatusCode::OK)
|
||||
.body("")
|
||||
.expect("mock");
|
||||
)
|
||||
.as_str(),
|
||||
StatusCode::OK,
|
||||
"",
|
||||
);
|
||||
}
|
||||
pub struct WebhookArgs<'a> {
|
||||
pub net: &'a MockNet,
|
||||
pub net: &'a mut network::MockNetwork,
|
||||
pub hostname: &'a Hostname,
|
||||
pub repo_path: &'a RepoPath,
|
||||
pub token: &'a str,
|
||||
|
@ -612,21 +573,21 @@ mod forgejo {
|
|||
|
||||
use git::RepoDetails;
|
||||
use git_next_core::server::RepoListenUrl;
|
||||
use kxio::net::{MockNet, StatusCode};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn a_commit_state(
|
||||
state: impl AsRef<str>,
|
||||
net: &MockNet,
|
||||
net: &mut MockNetwork,
|
||||
repo_details: &git::RepoDetails,
|
||||
commit: &git::Commit,
|
||||
) {
|
||||
net.on()
|
||||
.get(a_commit_status_url(repo_details, commit))
|
||||
.respond(StatusCode::OK)
|
||||
.body((json!({"state":state.as_ref()})).to_string())
|
||||
.expect("mock");
|
||||
let response = json!({"state":state.as_ref()});
|
||||
net.add_get_response(
|
||||
a_commit_status_url(repo_details, commit).as_str(),
|
||||
StatusCode::OK,
|
||||
response.to_string().as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn a_commit_status_url(
|
||||
|
@ -683,10 +644,9 @@ mod forgejo {
|
|||
|
||||
pub fn a_forgejo_forge(
|
||||
repo_details: &git::RepoDetails,
|
||||
net: impl Into<kxio::net::Net>,
|
||||
net: impl Into<kxio::network::Network>,
|
||||
) -> ForgeJo {
|
||||
let net: kxio::net::Net = net.into();
|
||||
ForgeJo::new(repo_details.clone(), net)
|
||||
ForgeJo::new(repo_details.clone(), net.into())
|
||||
}
|
||||
|
||||
pub fn a_forgejo_webhook_id() -> i64 {
|
||||
|
@ -714,8 +674,8 @@ mod forgejo {
|
|||
RepoAlias::new(a_name())
|
||||
}
|
||||
|
||||
pub fn a_network() -> kxio::net::MockNet {
|
||||
kxio::net::mock()
|
||||
pub fn a_network() -> kxio::network::MockNetwork {
|
||||
kxio::network::MockNetwork::new()
|
||||
}
|
||||
|
||||
pub fn a_name() -> String {
|
||||
|
@ -798,8 +758,8 @@ mod forgejo {
|
|||
git::commit::Sha::new(a_name())
|
||||
}
|
||||
|
||||
pub fn a_filesystem() -> kxio::fs::TempFileSystem {
|
||||
kxio::fs::temp().expect("temp fs")
|
||||
pub fn a_filesystem() -> kxio::fs::FileSystem {
|
||||
kxio::fs::temp().unwrap_or_else(|e| panic!("{}", e))
|
||||
}
|
||||
|
||||
pub fn repo_details(fs: &kxio::fs::FileSystem) -> git::RepoDetails {
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
//
|
||||
use git_next_core::{git, server::RepoListenUrl, WebhookId};
|
||||
use kxio::net::Net;
|
||||
|
||||
use kxio::network;
|
||||
|
||||
use crate::webhook::Hook;
|
||||
|
||||
pub async fn list(
|
||||
repo_details: &git::RepoDetails,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
net: &Net,
|
||||
net: &network::Network,
|
||||
) -> git::forge::webhook::Result<Vec<WebhookId>> {
|
||||
let mut ids: Vec<WebhookId> = vec![];
|
||||
let hostname = &repo_details.forge.hostname();
|
||||
|
@ -16,15 +17,22 @@ pub async fn list(
|
|||
loop {
|
||||
use secrecy::ExposeSecret;
|
||||
let token = &repo_details.forge.token().expose_secret();
|
||||
match net
|
||||
.get(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?page={page}&token={token}"
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
let url =
|
||||
format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?page={page}&token={token}");
|
||||
let net_url = network::NetUrl::new(url);
|
||||
let request = network::NetRequest::new(
|
||||
network::RequestMethod::Get,
|
||||
net_url,
|
||||
network::NetRequestHeaders::new(),
|
||||
network::RequestBody::None,
|
||||
network::ResponseType::Json,
|
||||
None,
|
||||
network::NetRequestLogging::None,
|
||||
);
|
||||
let result = net.get::<Vec<Hook>>(request).await;
|
||||
match result {
|
||||
Ok(response) => {
|
||||
if let Ok(list) = response.json::<Vec<Hook>>().await {
|
||||
if let Some(list) = response.response_body() {
|
||||
if list.is_empty() {
|
||||
return Ok(ids);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
//
|
||||
use git_next_core::{git, server::RepoListenUrl, RegisteredWebhook, WebhookAuth, WebhookId};
|
||||
|
||||
use kxio::net::Net;
|
||||
use kxio::network;
|
||||
use secrecy::ExposeSecret as _;
|
||||
use serde_json::json;
|
||||
use tracing::{info, instrument, warn};
|
||||
|
||||
use crate::webhook;
|
||||
|
@ -13,7 +12,7 @@ use crate::webhook::Hook;
|
|||
pub async fn register(
|
||||
repo_details: &git::RepoDetails,
|
||||
repo_listen_url: &RepoListenUrl,
|
||||
net: &Net,
|
||||
net: &network::Network,
|
||||
) -> git::forge::webhook::Result<RegisteredWebhook> {
|
||||
let Some(repo_config) = repo_details.repo_config.clone() else {
|
||||
return Err(git::forge::webhook::Error::NoRepoConfig);
|
||||
|
@ -28,27 +27,35 @@ pub async fn register(
|
|||
let hostname = &repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
let url = format!("https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}");
|
||||
let url = network::NetUrl::new(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks?token={token}"
|
||||
));
|
||||
let headers = network::NetRequestHeaders::new().with("Content-Type", "application/json");
|
||||
let authorisation = WebhookAuth::generate();
|
||||
match net
|
||||
.post(url)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(json!({
|
||||
"active": true,
|
||||
"authorization_header": authorisation.header_value(),
|
||||
"branch_filter": format!("{{{},{},{}}}", repo_config.branches().main(), repo_config.branches().next(), repo_config.branches().dev()),
|
||||
"config": {
|
||||
"content_type": "json",
|
||||
"url": repo_listen_url.to_string(),
|
||||
},
|
||||
"events": [ "push" ],
|
||||
"type": "forgejo"
|
||||
}).to_string())
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
let body = network::json!({
|
||||
"active": true,
|
||||
"authorization_header": authorisation.header_value(),
|
||||
"branch_filter": format!("{{{},{},{}}}", repo_config.branches().main(), repo_config.branches().next(), repo_config.branches().dev()),
|
||||
"config": {
|
||||
"content_type": "json",
|
||||
"url": repo_listen_url.to_string(),
|
||||
},
|
||||
"events": [ "push" ],
|
||||
"type": "forgejo"
|
||||
});
|
||||
let request = network::NetRequest::new(
|
||||
network::RequestMethod::Post,
|
||||
url,
|
||||
headers,
|
||||
network::RequestBody::Json(body),
|
||||
network::ResponseType::Json,
|
||||
None,
|
||||
network::NetRequestLogging::None,
|
||||
);
|
||||
let result = net.post_json::<Hook>(request).await;
|
||||
match result {
|
||||
Ok(response) => {
|
||||
let Ok(hook) = response.json::<Hook>().await else {
|
||||
let Some(hook) = response.response_body() else {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
// request response is Json so response_body never returns None
|
||||
return Err(git::forge::webhook::Error::NetworkResponseEmpty);
|
||||
|
@ -64,5 +71,4 @@ pub async fn register(
|
|||
Err(git::forge::webhook::Error::FailedToRegister(e.to_string()))
|
||||
}
|
||||
}
|
||||
// Ok(())
|
||||
}
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
//
|
||||
use git_next_core::{git, WebhookId};
|
||||
|
||||
use kxio::net::Net;
|
||||
use kxio::network;
|
||||
use secrecy::ExposeSecret as _;
|
||||
|
||||
pub async fn unregister(
|
||||
webhook_id: &WebhookId,
|
||||
repo_details: &git::RepoDetails,
|
||||
net: &Net,
|
||||
net: &network::Network,
|
||||
) -> git::forge::webhook::Result<()> {
|
||||
let hostname = &repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let token = repo_details.forge.token().expose_secret();
|
||||
match net
|
||||
.delete(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks/{webhook_id}?token={token}"
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
let url = network::NetUrl::new(format!(
|
||||
"https://{hostname}/api/v1/repos/{repo_path}/hooks/{webhook_id}?token={token}"
|
||||
));
|
||||
let request = network::NetRequest::new(
|
||||
network::RequestMethod::Delete,
|
||||
url,
|
||||
network::NetRequestHeaders::new(),
|
||||
network::RequestBody::None,
|
||||
network::ResponseType::None,
|
||||
None,
|
||||
network::NetRequestLogging::None,
|
||||
);
|
||||
match net.delete(request).await {
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to unregister webhook");
|
||||
Err(git::forge::webhook::Error::FailedToUnregister(
|
||||
|
|
|
@ -2,46 +2,53 @@
|
|||
use crate::{self as github, GithubState};
|
||||
use git_next_core::git::{self, forge::commit::Status};
|
||||
use github::GithubStatus;
|
||||
use kxio::network;
|
||||
|
||||
/// Checks the results of any (e.g. CI) status checks for the commit.
|
||||
///
|
||||
/// GitHub: <https://docs.github.com/en/rest/commits/statuses?apiVersion=2022-11-28#list-commit-statuses-for-a-reference>
|
||||
pub async fn status(
|
||||
github: &github::Github,
|
||||
commit: &git::Commit,
|
||||
) -> git::forge::webhook::Result<git::forge::commit::Status> {
|
||||
pub async fn status(github: &github::Github, commit: &git::Commit) -> git::forge::commit::Status {
|
||||
let repo_details = &github.repo_details;
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let url = format!("https://api.{hostname}/repos/{repo_path}/commits/{commit}/statuses");
|
||||
let Ok(response) = github
|
||||
.net
|
||||
.get(url)
|
||||
.headers(github::webhook::headers(repo_details.forge.token()))
|
||||
.body("")
|
||||
.send()
|
||||
.await
|
||||
else {
|
||||
return Ok(Status::Pending);
|
||||
};
|
||||
let statuses = response.json::<Vec<GithubStatus>>().await?;
|
||||
let result = statuses
|
||||
.into_iter()
|
||||
.map(|status| match status.state {
|
||||
GithubState::Success => Status::Pass,
|
||||
GithubState::Pending | GithubState::Blank => Status::Pending,
|
||||
GithubState::Failure | GithubState::Error => Status::Fail,
|
||||
})
|
||||
.reduce(|l, r| match (l, r) {
|
||||
(Status::Pass, Status::Pass) => Status::Pass,
|
||||
(_, Status::Fail) | (Status::Fail, _) => Status::Fail,
|
||||
(_, Status::Pending) | (Status::Pending, _) => Status::Pending,
|
||||
(Status::Error(e1), Status::Error(e2)) => Status::Error(format!("{e1} / {e2}")),
|
||||
(_, Status::Error(err)) | (Status::Error(err), _) => Status::Error(err),
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
tracing::warn!("No status checks configured for 'next' branch",);
|
||||
Status::Pass
|
||||
});
|
||||
Ok(result)
|
||||
let url = network::NetUrl::new(format!(
|
||||
"https://api.{hostname}/repos/{repo_path}/commits/{commit}/statuses"
|
||||
));
|
||||
let headers = github::webhook::headers(repo_details.forge.token());
|
||||
let request = network::NetRequest::new(
|
||||
network::RequestMethod::Get,
|
||||
url,
|
||||
headers,
|
||||
network::RequestBody::None,
|
||||
network::ResponseType::Json,
|
||||
None,
|
||||
network::NetRequestLogging::None,
|
||||
);
|
||||
let result = github.net.get::<Vec<GithubStatus>>(request).await;
|
||||
match result {
|
||||
Ok(response) => response
|
||||
.response_body()
|
||||
.and_then(|statuses| {
|
||||
statuses
|
||||
.into_iter()
|
||||
.map(|status| match status.state {
|
||||
GithubState::Success => Status::Pass,
|
||||
GithubState::Pending | GithubState::Blank => Status::Pending,
|
||||
GithubState::Failure | GithubState::Error => Status::Fail,
|
||||
})
|
||||
.reduce(|l, r| match (l, r) {
|
||||
(Status::Pass, Status::Pass) => Status::Pass,
|
||||
(_, Status::Fail) | (Status::Fail, _) => Status::Fail,
|
||||
(_, Status::Pending) | (Status::Pending, _) => Status::Pending,
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
tracing::warn!("No status checks configured for 'next' branch",);
|
||||
Status::Pass
|
||||
}),
|
||||
Err(e) => {
|
||||
tracing::warn!(?e, "Failed to get commit status");
|
||||
Status::Pending // assume issue is transient and allow retry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ mod webhook;
|
|||
|
||||
use crate as github;
|
||||
use git_next_core::{
|
||||
self as core, git,
|
||||
self as core,
|
||||
git::{self, forge::commit::Status},
|
||||
server::{self, RepoListenUrl},
|
||||
ForgeNotification, RegisteredWebhook, WebhookAuth, WebhookId,
|
||||
};
|
||||
|
@ -17,7 +18,7 @@ use derive_more::Constructor;
|
|||
#[derive(Clone, Debug, Constructor)]
|
||||
pub struct Github {
|
||||
repo_details: git::RepoDetails,
|
||||
net: kxio::net::Net,
|
||||
net: kxio::network::Network,
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl git::ForgeLike for Github {
|
||||
|
@ -51,10 +52,7 @@ impl git::ForgeLike for Github {
|
|||
github::webhook::parse_body(body)
|
||||
}
|
||||
|
||||
async fn commit_status(
|
||||
&self,
|
||||
commit: &git::Commit,
|
||||
) -> git::forge::webhook::Result<git::forge::commit::Status> {
|
||||
async fn commit_status(&self, commit: &git::Commit) -> Status {
|
||||
github::commit::status(self, commit).await
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
//
|
||||
#![allow(clippy::expect_used)]
|
||||
|
||||
use crate::{Github, GithubState, GithubStatus};
|
||||
use git_next_core::{
|
||||
git::{self, forge::commit::Status, ForgeLike},
|
||||
|
@ -11,7 +9,7 @@ use git_next_core::{
|
|||
};
|
||||
|
||||
use assert2::let_assert;
|
||||
use kxio::net::{MockNet, StatusCode};
|
||||
use kxio::network::{self, MockNetwork, StatusCode};
|
||||
use rand::RngCore;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
@ -101,10 +99,10 @@ mod github {
|
|||
let (states, expected) = $value;
|
||||
let repo_details = given::repo_details();
|
||||
let commit = given::a_commit();
|
||||
let net = given::a_network();
|
||||
given::commit_states(&states, &net, &repo_details, &commit);
|
||||
let mut net = given::a_network();
|
||||
given::commit_states(&states, &mut net, &repo_details, &commit);
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
assert_eq!(forge.commit_status(&commit).await.expect("status"), expected);
|
||||
assert_eq!(forge.commit_status(&commit).await, expected);
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
@ -127,37 +125,29 @@ mod github {
|
|||
);
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_return_pass_for_no_statuses() {
|
||||
async fn should_return_pending_for_no_statuses() {
|
||||
let repo_details = given::repo_details();
|
||||
let commit = given::a_commit();
|
||||
let net = given::a_network();
|
||||
net.on()
|
||||
.get(given::a_commit_status_url(&repo_details, &commit))
|
||||
.respond(StatusCode::OK)
|
||||
.body(json!([]).to_string())
|
||||
.expect("mock");
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
assert_eq!(
|
||||
forge.commit_status(&commit).await.expect("status"),
|
||||
Status::Pass // no CI checks configured
|
||||
let mut net = given::a_network();
|
||||
net.add_get_response(
|
||||
&given::a_commit_status_url(&repo_details, &commit),
|
||||
StatusCode::OK,
|
||||
"",
|
||||
);
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Pending);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_return_pending_for_network_error() {
|
||||
let repo_details = given::repo_details();
|
||||
let commit = given::a_commit();
|
||||
let net = given::a_network();
|
||||
net.on()
|
||||
.get(given::a_commit_status_url(&repo_details, &commit))
|
||||
.respond(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body("boom today")
|
||||
.expect("mock");
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
assert_eq!(
|
||||
forge.commit_status(&commit).await.expect("status"),
|
||||
Status::Pending
|
||||
let mut net = given::a_network();
|
||||
net.add_get_error(
|
||||
&given::a_commit_status_url(&repo_details, &commit),
|
||||
"boom today",
|
||||
);
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
assert_eq!(forge.commit_status(&commit).await, Status::Pending);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,9 +164,9 @@ mod github {
|
|||
let hook_id_1 = given::a_github_webhook_id();
|
||||
let hook_id_2 = given::a_github_webhook_id();
|
||||
let hook_id_3 = given::a_github_webhook_id();
|
||||
let net = given::a_network();
|
||||
let args = with::WebhookArgs {
|
||||
net: &net,
|
||||
let mut net = given::a_network();
|
||||
let mut args = with::WebhookArgs {
|
||||
net: &mut net,
|
||||
hostname,
|
||||
repo_path,
|
||||
};
|
||||
|
@ -188,10 +178,10 @@ mod github {
|
|||
with::ReturnedWebhook::new(hook_id_2, &repo_listen_url),
|
||||
with::ReturnedWebhook::new(hook_id_3, &given::any_webhook_url()),
|
||||
],
|
||||
&args,
|
||||
&mut args,
|
||||
);
|
||||
// page 2 with no items - stops pagination
|
||||
with::get_webhooks_by_page(2, &[], &args);
|
||||
with::get_webhooks_by_page(2, &[], &mut args);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
|
@ -211,14 +201,11 @@ mod github {
|
|||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let net = given::a_network();
|
||||
net.on()
|
||||
.get(format!(
|
||||
"https://api.{hostname}/repos/{repo_path}/hooks?page=1"
|
||||
))
|
||||
.respond(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body("error_message")
|
||||
.expect("mock");
|
||||
let mut net = given::a_network();
|
||||
net.add_get_error(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks?page=1").as_str(),
|
||||
"error_message",
|
||||
);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
|
@ -235,14 +222,12 @@ mod github {
|
|||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let webhook_id = given::a_webhook_id();
|
||||
let net = given::a_network();
|
||||
net.on()
|
||||
.delete(format!(
|
||||
"https://api.{hostname}/repos/{repo_path}/hooks/{webhook_id}"
|
||||
))
|
||||
.respond(StatusCode::OK)
|
||||
.body("")
|
||||
.expect("mock");
|
||||
let mut net = given::a_network();
|
||||
net.add_delete_response(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks/{webhook_id}").as_str(),
|
||||
StatusCode::OK,
|
||||
"",
|
||||
);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
|
@ -257,19 +242,16 @@ mod github {
|
|||
);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let net = given::a_network();
|
||||
let mut net = given::a_network();
|
||||
// unregister the webhook will return empty response
|
||||
let webhook_id = given::a_webhook_id();
|
||||
net.on()
|
||||
.delete(format!(
|
||||
"https://api.{hostname}/repos/{repo_path}/hooks/{webhook_id}"
|
||||
))
|
||||
.respond(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.mock()
|
||||
.expect("mock");
|
||||
net.add_delete_error(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks").as_str(),
|
||||
"error",
|
||||
);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
let webhook_id = given::a_webhook_id();
|
||||
let_assert!(Err(err) = forge.unregister_webhook(&webhook_id).await);
|
||||
assert!(
|
||||
matches!(err, git::forge::webhook::Error::FailedToRegister(_)),
|
||||
|
@ -292,24 +274,23 @@ mod github {
|
|||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let net = given::a_network();
|
||||
let args = with::WebhookArgs {
|
||||
net: &net,
|
||||
let mut net = given::a_network();
|
||||
let mut args = with::WebhookArgs {
|
||||
net: &mut net,
|
||||
hostname,
|
||||
repo_path,
|
||||
};
|
||||
// there are no existing matching webhooks
|
||||
with::get_webhooks_by_page(1, &[], &args);
|
||||
with::get_webhooks_by_page(1, &[], &mut args);
|
||||
// register the webhook will succeed
|
||||
let webhook_id = given::a_github_webhook_id();
|
||||
net.on()
|
||||
.post(format!("https://api.{hostname}/repos/{repo_path}/hooks"))
|
||||
.respond(StatusCode::OK)
|
||||
.body(
|
||||
json!({"id": webhook_id, "config":{"url": repo_listen_url.to_string()}})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("mock");
|
||||
net.add_post_response(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks").as_str(),
|
||||
StatusCode::OK,
|
||||
json!({"id": webhook_id, "config":{"url": repo_listen_url.to_string()}})
|
||||
.to_string()
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
|
@ -348,9 +329,9 @@ mod github {
|
|||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let net = given::a_network();
|
||||
let args = with::WebhookArgs {
|
||||
net: &net,
|
||||
let mut net = given::a_network();
|
||||
let mut args = with::WebhookArgs {
|
||||
net: &mut net,
|
||||
hostname,
|
||||
repo_path,
|
||||
};
|
||||
|
@ -360,21 +341,20 @@ mod github {
|
|||
with::ReturnedWebhook::new(given::a_github_webhook_id(), &given::any_webhook_url());
|
||||
let hooks = [hook_1, hook_2, hook_3];
|
||||
// there are three existing webhooks, two are matching webhooks
|
||||
with::get_webhooks_by_page(1, &hooks, &args);
|
||||
with::get_webhooks_by_page(2, &[], &args);
|
||||
with::get_webhooks_by_page(1, &hooks, &mut args);
|
||||
with::get_webhooks_by_page(2, &[], &mut args);
|
||||
// should unregister 1 and 2, but not 3
|
||||
with::unregister_webhook(&hooks[0], &args);
|
||||
with::unregister_webhook(&hooks[1], &args);
|
||||
with::unregister_webhook(&hooks[0], &mut args);
|
||||
with::unregister_webhook(&hooks[1], &mut args);
|
||||
// register the webhook will succeed
|
||||
let webhook_id = given::a_github_webhook_id();
|
||||
net.on()
|
||||
.post(format!("https://api.{hostname}/repos/{repo_path}/hooks"))
|
||||
.respond(StatusCode::OK)
|
||||
.body(
|
||||
json!({"id": webhook_id, "config":{"url": repo_listen_url.to_string()}})
|
||||
.to_string(),
|
||||
)
|
||||
.expect("mock");
|
||||
net.add_post_response(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks").as_str(),
|
||||
StatusCode::OK,
|
||||
json!({"id": webhook_id, "config":{"url": repo_listen_url.to_string()}})
|
||||
.to_string()
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
|
@ -395,26 +375,26 @@ mod github {
|
|||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let net = given::a_network();
|
||||
let args = with::WebhookArgs {
|
||||
net: &net,
|
||||
let mut net = given::a_network();
|
||||
let mut args = with::WebhookArgs {
|
||||
net: &mut net,
|
||||
hostname,
|
||||
repo_path,
|
||||
};
|
||||
// there are no existing matching webhooks
|
||||
with::get_webhooks_by_page(1, &[], &args);
|
||||
with::get_webhooks_by_page(1, &[], &mut args);
|
||||
// register the webhook will return empty response
|
||||
net.on()
|
||||
.post(format!("https://api.{hostname}/repos/{repo_path}/hooks"))
|
||||
.respond(StatusCode::OK)
|
||||
.body(json!({}).to_string())
|
||||
.expect("mock");
|
||||
net.add_post_response(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks").as_str(),
|
||||
StatusCode::OK,
|
||||
json!({}).to_string().as_str(), // empty response
|
||||
);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
let_assert!(Err(err) = forge.register_webhook(&repo_listen_url).await);
|
||||
assert!(
|
||||
matches!(err, git::forge::webhook::Error::NetworkResponseEmpty),
|
||||
matches!(err, git::forge::webhook::Error::FailedToRegister(_)),
|
||||
"{err:?}"
|
||||
);
|
||||
}
|
||||
|
@ -429,20 +409,19 @@ mod github {
|
|||
let repo_listen_url = given::a_repo_listen_url(&repo_details);
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let repo_path = &repo_details.repo_path;
|
||||
let net = given::a_network();
|
||||
let args = with::WebhookArgs {
|
||||
net: &net,
|
||||
let mut net = given::a_network();
|
||||
let mut args = with::WebhookArgs {
|
||||
net: &mut net,
|
||||
hostname,
|
||||
repo_path,
|
||||
};
|
||||
// there are no existing matching webhooks
|
||||
with::get_webhooks_by_page(1, &[], &args);
|
||||
with::get_webhooks_by_page(1, &[], &mut args);
|
||||
// register the webhook will return empty response
|
||||
net.on()
|
||||
.post(format!("https://api.{hostname}/repos/{repo_path}/hooks"))
|
||||
.respond(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body("error")
|
||||
.expect("mock");
|
||||
net.add_post_error(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks").as_str(),
|
||||
"error",
|
||||
);
|
||||
|
||||
let forge = given::a_github_forge(&repo_details, net);
|
||||
|
||||
|
@ -459,34 +438,32 @@ mod github {
|
|||
|
||||
use super::*;
|
||||
|
||||
pub fn get_webhooks_by_page(page: u8, response: &[ReturnedWebhook], args: &WebhookArgs) {
|
||||
pub fn get_webhooks_by_page(
|
||||
page: u8,
|
||||
response: &[ReturnedWebhook],
|
||||
args: &mut WebhookArgs,
|
||||
) {
|
||||
let hostname = args.hostname;
|
||||
let repo_path = args.repo_path;
|
||||
args.net
|
||||
.on()
|
||||
.get(format!(
|
||||
"https://api.{hostname}/repos/{repo_path}/hooks?page={page}"
|
||||
))
|
||||
.respond(StatusCode::OK)
|
||||
.body(json!(response).to_string())
|
||||
.expect("mock");
|
||||
args.net.add_get_response(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks?page={page}").as_str(),
|
||||
StatusCode::OK,
|
||||
json!(response).to_string().as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn unregister_webhook(hook1: &ReturnedWebhook, args: &WebhookArgs) {
|
||||
pub fn unregister_webhook(hook1: &ReturnedWebhook, args: &mut WebhookArgs) {
|
||||
let webhook_id = hook1.id;
|
||||
let hostname = args.hostname;
|
||||
let repo_path = args.repo_path;
|
||||
args.net
|
||||
.on()
|
||||
.delete(format!(
|
||||
"https://api.{hostname}/repos/{repo_path}/hooks/{webhook_id}"
|
||||
))
|
||||
.respond(StatusCode::OK)
|
||||
.body("")
|
||||
.expect("mock");
|
||||
args.net.add_delete_response(
|
||||
format!("https://api.{hostname}/repos/{repo_path}/hooks/{webhook_id}").as_str(),
|
||||
StatusCode::OK,
|
||||
"",
|
||||
);
|
||||
}
|
||||
pub struct WebhookArgs<'a> {
|
||||
pub net: &'a kxio::net::MockNet,
|
||||
pub net: &'a mut network::MockNetwork,
|
||||
pub hostname: &'a Hostname,
|
||||
pub repo_path: &'a RepoPath,
|
||||
}
|
||||
|
@ -520,23 +497,21 @@ mod github {
|
|||
|
||||
pub fn commit_states(
|
||||
states: &[GithubState],
|
||||
net: &MockNet,
|
||||
net: &mut MockNetwork,
|
||||
repo_details: &git::RepoDetails,
|
||||
commit: &git::Commit,
|
||||
) {
|
||||
net.on()
|
||||
.get(a_commit_status_url(repo_details, commit))
|
||||
.respond(StatusCode::OK)
|
||||
.body(
|
||||
json!(states
|
||||
.iter()
|
||||
.map(|state| GithubStatus {
|
||||
state: state.to_owned()
|
||||
})
|
||||
.collect::<Vec<_>>())
|
||||
.to_string(),
|
||||
)
|
||||
.expect("mock");
|
||||
let response = json!(states
|
||||
.iter()
|
||||
.map(|state| GithubStatus {
|
||||
state: state.to_owned()
|
||||
})
|
||||
.collect::<Vec<_>>());
|
||||
net.add_get_response(
|
||||
a_commit_status_url(repo_details, commit).as_str(),
|
||||
StatusCode::OK,
|
||||
response.to_string().as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn a_commit_status_url(
|
||||
|
@ -603,11 +578,10 @@ mod github {
|
|||
|
||||
pub fn a_github_forge(
|
||||
repo_details: &git::RepoDetails,
|
||||
net: impl Into<kxio::net::Net>,
|
||||
net: impl Into<kxio::network::Network>,
|
||||
) -> Github {
|
||||
Github::new(repo_details.clone(), net.into())
|
||||
}
|
||||
|
||||
pub fn repo_details() -> git::RepoDetails {
|
||||
git::RepoDetails::new(
|
||||
git::Generation::default(),
|
||||
|
@ -640,8 +614,8 @@ mod github {
|
|||
pub fn a_repo_alias() -> RepoAlias {
|
||||
RepoAlias::new(a_name())
|
||||
}
|
||||
pub fn a_network() -> kxio::net::MockNet {
|
||||
kxio::net::mock()
|
||||
pub fn a_network() -> kxio::network::MockNetwork {
|
||||
kxio::network::MockNetwork::new()
|
||||
}
|
||||
|
||||
pub fn a_repo_listen_url(repo_details: &RepoDetails) -> RepoListenUrl {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
use crate as github;
|
||||
use git_next_core::{git, server::RepoListenUrl, WebhookId};
|
||||
|
||||
use kxio::network;
|
||||
|
||||
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#list-repository-webhooks
|
||||
pub async fn list(
|
||||
github: &github::Github,
|
||||
|
@ -13,36 +15,40 @@ pub async fn list(
|
|||
let net = &github.net;
|
||||
let mut page = 1;
|
||||
loop {
|
||||
let result = net
|
||||
.get(format!(
|
||||
let request = network::NetRequest::new(
|
||||
network::RequestMethod::Get,
|
||||
network::NetUrl::new(format!(
|
||||
"https://api.{hostname}/repos/{}/hooks?page={page}",
|
||||
repo_details.repo_path,
|
||||
))
|
||||
.headers(github::webhook::headers(repo_details.forge.token()))
|
||||
.send()
|
||||
.await;
|
||||
)),
|
||||
github::webhook::headers(repo_details.forge.token()),
|
||||
network::RequestBody::None,
|
||||
network::ResponseType::Json,
|
||||
None,
|
||||
network::NetRequestLogging::None,
|
||||
);
|
||||
let result = net.get::<Vec<github::GithubHook>>(request).await;
|
||||
match result {
|
||||
Ok(response) => match response.json::<Vec<github::GithubHook>>().await {
|
||||
Err(err) => {
|
||||
tracing::warn!(?err, "failed");
|
||||
Ok(response) => {
|
||||
let Some(list) = response.response_body() else {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
// request response is Json so response_body never returns None
|
||||
return Err(git::forge::webhook::Error::NetworkResponseEmpty);
|
||||
};
|
||||
if list.is_empty() {
|
||||
return Ok(ids);
|
||||
}
|
||||
Ok(list) => {
|
||||
if list.is_empty() {
|
||||
return Ok(ids);
|
||||
for hook in list {
|
||||
if hook
|
||||
.url()
|
||||
.as_ref()
|
||||
.starts_with(&repo_listen_url.to_string())
|
||||
{
|
||||
ids.push(hook.id());
|
||||
}
|
||||
for hook in list {
|
||||
if hook
|
||||
.url()
|
||||
.as_ref()
|
||||
.starts_with(&repo_listen_url.to_string())
|
||||
{
|
||||
ids.push(hook.id());
|
||||
}
|
||||
}
|
||||
page += 1;
|
||||
}
|
||||
},
|
||||
page += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(git::forge::webhook::Error::Network(e));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
//
|
||||
use git_next_core::{git, webhook, ApiToken, BranchName};
|
||||
|
||||
|
@ -18,24 +16,19 @@ pub use unregister::unregister;
|
|||
#[cfg(test)]
|
||||
pub use authorisation::sign_body;
|
||||
|
||||
pub fn headers(token: &ApiToken) -> HashMap<String, String> {
|
||||
pub fn headers(token: &ApiToken) -> kxio::network::NetRequestHeaders {
|
||||
use secrecy::ExposeSecret;
|
||||
|
||||
HashMap::from([
|
||||
(
|
||||
"Accept".to_string(),
|
||||
"application/vnd.github+json".to_string(),
|
||||
),
|
||||
(
|
||||
"User-Agent".to_string(),
|
||||
format!("git-next/server/{}", clap::crate_version!()),
|
||||
),
|
||||
(
|
||||
"Authorization".to_string(),
|
||||
format!("Bearer {}", token.expose_secret()),
|
||||
),
|
||||
("X-GitHub-Api-Version".to_string(), "2022-11-28".to_string()),
|
||||
])
|
||||
kxio::network::NetRequestHeaders::default()
|
||||
.with("Accept", "application/vnd.github+json")
|
||||
.with(
|
||||
"User-Agent",
|
||||
format!("git-next/server/{}", clap::crate_version!()).as_str(),
|
||||
)
|
||||
.with(
|
||||
"Authorization",
|
||||
format!("Bearer {}", token.expose_secret()).as_str(),
|
||||
)
|
||||
.with("X-GitHub-Api-Version", "2022-11-28")
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
//
|
||||
use crate::{self as github, webhook};
|
||||
use git_next_core::{git, server::RepoListenUrl, RegisteredWebhook, WebhookAuth, WebhookId};
|
||||
use serde_json::json;
|
||||
|
||||
use kxio::network;
|
||||
|
||||
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#create-a-repository-webhook
|
||||
pub async fn register(
|
||||
|
@ -22,48 +23,45 @@ pub async fn register(
|
|||
let net = &github.net;
|
||||
let hostname = repo_details.forge.hostname();
|
||||
let authorisation = WebhookAuth::generate();
|
||||
match net
|
||||
.post(format!(
|
||||
let request = network::NetRequest::new(
|
||||
network::RequestMethod::Post,
|
||||
network::NetUrl::new(format!(
|
||||
"https://api.{hostname}/repos/{}/hooks",
|
||||
repo_details.repo_path
|
||||
))
|
||||
.headers(github::webhook::headers(repo_details.forge.token()))
|
||||
.body(
|
||||
json!({
|
||||
"name": "web",
|
||||
"active": true,
|
||||
"events": ["push"],
|
||||
"config": {
|
||||
"url": repo_listen_url.to_string(),
|
||||
"content_type": "json",
|
||||
"secret": authorisation.to_string(),
|
||||
"insecure_ssl": "0",
|
||||
}
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
)),
|
||||
github::webhook::headers(repo_details.forge.token()),
|
||||
network::RequestBody::Json(network::json!({
|
||||
"name": "web",
|
||||
"active": true,
|
||||
"events": ["push"],
|
||||
"config": {
|
||||
"url": repo_listen_url.to_string(),
|
||||
"content_type": "json",
|
||||
"secret": authorisation.to_string(),
|
||||
"insecure_ssl": "0",
|
||||
}
|
||||
})),
|
||||
network::ResponseType::Json,
|
||||
None,
|
||||
network::NetRequestLogging::None,
|
||||
);
|
||||
let result = net.post_json::<github::GithubHook>(request).await;
|
||||
match result {
|
||||
Ok(response) => {
|
||||
let Some(hook) = response.response_body() else {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
// request response is Json so response_body never returns None
|
||||
return Err(git::forge::webhook::Error::NetworkResponseEmpty);
|
||||
};
|
||||
tracing::info!(webhook_id = %hook.id, "Webhook registered");
|
||||
Ok(RegisteredWebhook::new(
|
||||
WebhookId::new(format!("{}", hook.id)),
|
||||
authorisation,
|
||||
))
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to register webhook");
|
||||
Err(git::forge::webhook::Error::FailedToRegister(e.to_string()))
|
||||
}
|
||||
Ok(response) => {
|
||||
match response.json::<github::GithubHook>().await {
|
||||
Err(_) => {
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
// request response is Json so response_body never returns None
|
||||
return Err(git::forge::webhook::Error::NetworkResponseEmpty);
|
||||
}
|
||||
Ok(hook) => {
|
||||
tracing::info!(webhook_id = %hook.id, "Webhook registered");
|
||||
Ok(RegisteredWebhook::new(
|
||||
WebhookId::new(format!("{}", hook.id)),
|
||||
authorisation,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
use crate as github;
|
||||
use git_next_core::{git, WebhookId};
|
||||
|
||||
use kxio::network;
|
||||
|
||||
// https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#delete-a-repository-webhook
|
||||
pub async fn unregister(
|
||||
github: &github::Github,
|
||||
|
@ -10,13 +12,20 @@ pub async fn unregister(
|
|||
let net = &github.net;
|
||||
let repo_details = &github.repo_details;
|
||||
let hostname = repo_details.forge.hostname();
|
||||
net.delete(format!(
|
||||
"https://api.{hostname}/repos/{}/hooks/{}",
|
||||
repo_details.repo_path, webhook_id
|
||||
))
|
||||
.headers(github::webhook::headers(repo_details.forge.token()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| git::forge::webhook::Error::FailedToRegister(e.to_string()))
|
||||
.map(|_| ())
|
||||
let request = network::NetRequest::new(
|
||||
network::RequestMethod::Delete,
|
||||
network::NetUrl::new(format!(
|
||||
"https://api.{hostname}/repos/{}/hooks/{}",
|
||||
repo_details.repo_path, webhook_id
|
||||
)),
|
||||
github::webhook::headers(repo_details.forge.token()),
|
||||
network::RequestBody::None,
|
||||
network::ResponseType::None,
|
||||
None,
|
||||
network::NetRequestLogging::None,
|
||||
);
|
||||
net.delete(request)
|
||||
.await
|
||||
.map_err(|e| git::forge::webhook::Error::FailedToRegister(e.to_string()))
|
||||
.map(|_| ())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue