Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
httpd: Move `test` utility mod from inside `api` to root
xphoniex committed 3 years ago
commit b0b94fea1893de5f15bb668a5ec3a802bc10d167
parent 1185a1fc6bf36cf5417949fc2e61ec38836cb459
9 files changed +254 -252
modified radicle-httpd/src/api.rs
@@ -1,5 +1,4 @@
-
#[cfg(test)]
-
pub mod test;
+
pub mod auth;

use std::collections::HashMap;
use std::sync::Arc;
@@ -21,7 +20,6 @@ use radicle::identity::Id;
use radicle::storage::{ReadRepository, ReadStorage};
use radicle::Profile;

-
mod auth;
mod axum_extra;
mod error;
mod json;
@@ -70,6 +68,11 @@ impl Context {
    pub fn profile(&self) -> &Arc<Profile> {
        &self.profile
    }
+

+
    #[cfg(test)]
+
    pub fn sessions(&self) -> &Arc<RwLock<HashMap<SessionId, auth::Session>>> {
+
        &self.sessions
+
    }
}

pub fn router(ctx: Context) -> Router {
deleted radicle-httpd/src/api/test.rs
@@ -1,241 +0,0 @@
-
use std::convert::TryInto as _;
-
use std::path::Path;
-
use std::str::FromStr;
-
use std::sync::Arc;
-
use std::{env, fs};
-

-
use axum::body::Body;
-
use axum::http::{Method, Request};
-
use axum::Router;
-
use serde_json::Value;
-
use time::OffsetDateTime;
-
use tower::ServiceExt;
-

-
use radicle::cob::issue::Issues;
-
use radicle::cob::patch::{MergeTarget, Patches};
-
use radicle::git::raw as git2;
-
use radicle::storage::ReadStorage;
-
use radicle_cli::commands::rad_init;
-
use radicle_crypto::ssh::keystore::MemorySigner;
-
use radicle_crypto::Signer;
-

-
use crate::api::{auth, Context};
-

-
pub const HEAD: &str = "1e978d19f251cd9821d9d9a76d1bd436bf0690d5";
-
pub const HEAD_1: &str = "f604ce9fd5b7cc77b7609beda45ea8760bee78f7";
-
pub const PATCH_ID: &str = "4250f0117659ee4de9af99e699a63395cd6ffa1c";
-
pub const ISSUE_ID: &str = "8adca8aad2a2cb99b9847d20193930cde2042a57";
-

-
const PASSWORD: &str = "radicle";
-

-
pub fn seed(dir: &Path) -> Context {
-
    let workdir = dir.join("hello-world");
-
    let rad_home = dir.join("radicle");
-

-
    env::set_var("RAD_PASSPHRASE", PASSWORD);
-
    env::set_var(
-
        "RAD_SEED",
-
        "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
-
    );
-

-
    fs::create_dir_all(&workdir).unwrap();
-
    fs::create_dir_all(&rad_home).unwrap();
-

-
    // add commits to workdir (repo)
-
    let repo = git2::Repository::init(&workdir).unwrap();
-
    let tree =
-
        radicle::git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), &repo).unwrap();
-

-
    let sig_time = git2::Time::new(1673001014, 0);
-
    let sig = git2::Signature::new("Alice Liddell", "alice@radicle.xyz", &sig_time).unwrap();
-

-
    let oid = repo
-
        .commit(Some("HEAD"), &sig, &sig, "Initial commit\n", &tree, &[])
-
        .unwrap();
-
    let commit = repo.find_commit(oid).unwrap();
-

-
    repo.checkout_tree(tree.as_object(), None).unwrap();
-

-
    fs::create_dir(workdir.join("dir1")).unwrap();
-
    fs::write(
-
        workdir.join("dir1").join("README"),
-
        "Hello World from dir1!\n",
-
    )
-
    .unwrap();
-
    let mut index = repo.index().unwrap();
-
    index
-
        .add_all(["."], git2::IndexAddOption::DEFAULT, None)
-
        .unwrap();
-
    index.write().unwrap();
-

-
    let oid = index.write_tree().unwrap();
-
    let tree = repo.find_tree(oid).unwrap();
-

-
    repo.commit(
-
        Some("HEAD"),
-
        &sig,
-
        &sig,
-
        "Add another folder\n",
-
        &tree,
-
        &[&commit],
-
    )
-
    .unwrap();
-

-
    // eq. rad auth
-
    let profile =
-
        radicle::Profile::init(rad_home.try_into().unwrap(), PASSWORD.to_owned()).unwrap();
-

-
    // rad init
-
    rad_init::init(
-
        rad_init::Options {
-
            path: Some(workdir.clone()),
-
            name: Some("hello-world".to_string()),
-
            description: Some("Rad repository for tests".to_string()),
-
            branch: None,
-
            interactive: false.into(),
-
            setup_signing: false,
-
            set_upstream: false,
-
            sync: false,
-
            track: false,
-
        },
-
        &profile,
-
    )
-
    .unwrap();
-

-
    // eq. rad issue new
-
    env::set_var("RAD_COMMIT_TIME", "1673001014");
-

-
    let signer = MemorySigner::load(&profile.keystore, PASSWORD.to_owned().into()).unwrap();
-
    let storage = &profile.storage;
-
    let (_, id) = radicle::rad::repo(&workdir).unwrap();
-
    let repo = storage.repository(id).unwrap();
-
    let mut issues = Issues::open(*signer.public_key(), &repo).unwrap();
-
    issues
-
        .create(
-
            "Issue #1".to_string(),
-
            "Change 'hello world' to 'hello everyone'".to_string(),
-
            &[],
-
            &[],
-
            &signer,
-
        )
-
        .unwrap();
-

-
    // eq. rad patch open
-
    let mut patches = Patches::open(*signer.public_key(), &repo).unwrap();
-
    let oid = radicle::git::Oid::from_str(HEAD).unwrap();
-
    let base = radicle::git::Oid::from_str(HEAD_1).unwrap();
-
    patches
-
        .create(
-
            "A new `hello word`",
-
            "change `hello world` in README to something else",
-
            MergeTarget::Delegates,
-
            base,
-
            oid,
-
            &[],
-
            &signer,
-
        )
-
        .unwrap();
-

-
    Context {
-
        profile: Arc::new(profile),
-
        sessions: Default::default(),
-
    }
-
}
-

-
/// Adds an authorized session to the Context::sessions HashMap.
-
pub async fn create_session(ctx: Context) {
-
    let issued_at = OffsetDateTime::now_utc();
-
    let mut sessions = ctx.sessions.write().await;
-
    sessions.insert(
-
        String::from("u9MGAkkfkMOv0uDDB2WeUHBT7HbsO2Dy"),
-
        auth::Session {
-
            status: auth::AuthState::Authorized,
-
            public_key: ctx.profile.public_key,
-
            issued_at,
-
            expires_at: issued_at
-
                .checked_add(auth::AUTHORIZED_SESSIONS_EXPIRATION)
-
                .unwrap(),
-
        },
-
    );
-
}
-

-
pub async fn get(app: &Router, path: impl ToString) -> Response {
-
    Response(
-
        app.clone()
-
            .oneshot(request(path, Method::GET, None, None))
-
            .await
-
            .unwrap(),
-
    )
-
}
-

-
pub async fn post(
-
    app: &Router,
-
    path: impl ToString,
-
    body: Option<Body>,
-
    auth: Option<String>,
-
) -> Response {
-
    Response(
-
        app.clone()
-
            .oneshot(request(path, Method::POST, body, auth))
-
            .await
-
            .unwrap(),
-
    )
-
}
-

-
pub async fn patch(
-
    app: &Router,
-
    path: impl ToString,
-
    body: Option<Body>,
-
    auth: Option<String>,
-
) -> Response {
-
    Response(
-
        app.clone()
-
            .oneshot(request(path, Method::PATCH, body, auth))
-
            .await
-
            .unwrap(),
-
    )
-
}
-

-
pub async fn put(
-
    app: &Router,
-
    path: impl ToString,
-
    body: Option<Body>,
-
    auth: Option<String>,
-
) -> Response {
-
    Response(
-
        app.clone()
-
            .oneshot(request(path, Method::PUT, body, auth))
-
            .await
-
            .unwrap(),
-
    )
-
}
-

-
fn request(
-
    path: impl ToString,
-
    method: Method,
-
    body: Option<Body>,
-
    auth: Option<String>,
-
) -> Request<Body> {
-
    let mut request = Request::builder()
-
        .method(method)
-
        .uri(path.to_string())
-
        .header("Content-Type", "application/json");
-
    if let Some(token) = auth {
-
        request = request.header("Authorization", format!("Bearer {token}"));
-
    }
-

-
    request.body(body.unwrap_or_else(Body::empty)).unwrap()
-
}
-

-
pub struct Response(axum::response::Response);
-

-
impl Response {
-
    pub async fn json(self) -> Value {
-
        let body = hyper::body::to_bytes(self.0.into_body()).await.unwrap();
-
        serde_json::from_slice(&body).unwrap()
-
    }
-

-
    pub fn status(&self) -> axum::http::StatusCode {
-
        self.0.status()
-
    }
-
}
modified radicle-httpd/src/api/v1/delegates.rs
@@ -74,7 +74,7 @@ mod routes {
    use axum::http::StatusCode;
    use serde_json::json;

-
    use crate::api::test::{self, get, HEAD};
+
    use crate::test::{self, get, HEAD};

    #[tokio::test]
    async fn test_delegates_projects() {
modified radicle-httpd/src/api/v1/projects.rs
@@ -562,7 +562,7 @@ mod routes {
    use pretty_assertions::assert_eq;
    use serde_json::json;

-
    use crate::api::test::{self, get, patch, post, HEAD, HEAD_1, ISSUE_ID, PATCH_ID};
+
    use crate::test::{self, get, patch, post, HEAD, HEAD_1, ISSUE_ID, PATCH_ID};

    const CREATED_ISSUE_ID: &str = "b56febfba1e7dd20f4aea43ca2fe9dcf1fd448ba";

modified radicle-httpd/src/api/v1/sessions.rs
@@ -125,10 +125,8 @@ mod routes {
    use axum::http::StatusCode;
    use radicle_cli::commands::rad_web::{self, SessionInfo};

-
    use crate::api::{
-
        auth::{AuthState, Session},
-
        test::{self, get, post, put},
-
    };
+
    use crate::api::auth::{AuthState, Session};
+
    use crate::test::{self, get, post, put};

    #[tokio::test]
    async fn test_session() {
modified radicle-httpd/src/api/v1/stats.rs
@@ -29,7 +29,7 @@ mod routes {
    use axum::http::StatusCode;
    use serde_json::json;

-
    use crate::api::test::{self, get};
+
    use crate::test::{self, get};

    #[tokio::test]
    async fn test_stats() {
modified radicle-httpd/src/git.rs
@@ -203,7 +203,7 @@ mod routes {
    use axum::http::{Method, StatusCode};
    use tower::ServiceExt;

-
    use crate::api::test;
+
    use crate::test;

    #[tokio::test]
    async fn test_invalid_route_returns_404() {
modified radicle-httpd/src/lib.rs
@@ -17,6 +17,8 @@ use tracing::Span;

mod api;
mod git;
+
#[cfg(test)]
+
mod test;

#[derive(Debug, Clone)]
pub struct Options {
@@ -30,9 +32,11 @@ pub async fn run(options: Options) -> anyhow::Result<()> {
        .output()
        .context("'git' command must be available")?
        .stdout;
+

    tracing::info!("{}", str::from_utf8(&git_version)?.trim());

    let profile = Arc::new(radicle::Profile::load()?);
+

    tracing::info!("using radicle home at {}", profile.home().display());

    let ctx = api::Context::new(profile.clone());
added radicle-httpd/src/test.rs
@@ -0,0 +1,238 @@
+
use std::convert::TryInto as _;
+
use std::path::Path;
+
use std::str::FromStr;
+
use std::sync::Arc;
+
use std::{env, fs};
+

+
use axum::body::Body;
+
use axum::http::{Method, Request};
+
use axum::Router;
+
use serde_json::Value;
+
use time::OffsetDateTime;
+
use tower::ServiceExt;
+

+
use radicle::cob::issue::Issues;
+
use radicle::cob::patch::{MergeTarget, Patches};
+
use radicle::git::raw as git2;
+
use radicle::storage::ReadStorage;
+
use radicle_cli::commands::rad_init;
+
use radicle_crypto::ssh::keystore::MemorySigner;
+
use radicle_crypto::Signer;
+

+
use crate::api::{auth, Context};
+

+
pub const HEAD: &str = "1e978d19f251cd9821d9d9a76d1bd436bf0690d5";
+
pub const HEAD_1: &str = "f604ce9fd5b7cc77b7609beda45ea8760bee78f7";
+
pub const PATCH_ID: &str = "4250f0117659ee4de9af99e699a63395cd6ffa1c";
+
pub const ISSUE_ID: &str = "8adca8aad2a2cb99b9847d20193930cde2042a57";
+

+
const PASSWORD: &str = "radicle";
+

+
pub fn seed(dir: &Path) -> Context {
+
    let workdir = dir.join("hello-world");
+
    let rad_home = dir.join("radicle");
+

+
    env::set_var("RAD_PASSPHRASE", PASSWORD);
+
    env::set_var(
+
        "RAD_SEED",
+
        "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+
    );
+

+
    fs::create_dir_all(&workdir).unwrap();
+
    fs::create_dir_all(&rad_home).unwrap();
+

+
    // add commits to workdir (repo)
+
    let repo = git2::Repository::init(&workdir).unwrap();
+
    let tree =
+
        radicle::git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), &repo).unwrap();
+

+
    let sig_time = git2::Time::new(1673001014, 0);
+
    let sig = git2::Signature::new("Alice Liddell", "alice@radicle.xyz", &sig_time).unwrap();
+

+
    let oid = repo
+
        .commit(Some("HEAD"), &sig, &sig, "Initial commit\n", &tree, &[])
+
        .unwrap();
+
    let commit = repo.find_commit(oid).unwrap();
+

+
    repo.checkout_tree(tree.as_object(), None).unwrap();
+

+
    fs::create_dir(workdir.join("dir1")).unwrap();
+
    fs::write(
+
        workdir.join("dir1").join("README"),
+
        "Hello World from dir1!\n",
+
    )
+
    .unwrap();
+
    let mut index = repo.index().unwrap();
+
    index
+
        .add_all(["."], git2::IndexAddOption::DEFAULT, None)
+
        .unwrap();
+
    index.write().unwrap();
+

+
    let oid = index.write_tree().unwrap();
+
    let tree = repo.find_tree(oid).unwrap();
+

+
    repo.commit(
+
        Some("HEAD"),
+
        &sig,
+
        &sig,
+
        "Add another folder\n",
+
        &tree,
+
        &[&commit],
+
    )
+
    .unwrap();
+

+
    // eq. rad auth
+
    let profile =
+
        radicle::Profile::init(rad_home.try_into().unwrap(), PASSWORD.to_owned()).unwrap();
+

+
    // rad init
+
    rad_init::init(
+
        rad_init::Options {
+
            path: Some(workdir.clone()),
+
            name: Some("hello-world".to_string()),
+
            description: Some("Rad repository for tests".to_string()),
+
            branch: None,
+
            interactive: false.into(),
+
            setup_signing: false,
+
            set_upstream: false,
+
            sync: false,
+
            track: false,
+
        },
+
        &profile,
+
    )
+
    .unwrap();
+

+
    // eq. rad issue new
+
    env::set_var("RAD_COMMIT_TIME", "1673001014");
+

+
    let signer = MemorySigner::load(&profile.keystore, PASSWORD.to_owned().into()).unwrap();
+
    let storage = &profile.storage;
+
    let (_, id) = radicle::rad::repo(&workdir).unwrap();
+
    let repo = storage.repository(id).unwrap();
+
    let mut issues = Issues::open(*signer.public_key(), &repo).unwrap();
+
    issues
+
        .create(
+
            "Issue #1".to_string(),
+
            "Change 'hello world' to 'hello everyone'".to_string(),
+
            &[],
+
            &[],
+
            &signer,
+
        )
+
        .unwrap();
+

+
    // eq. rad patch open
+
    let mut patches = Patches::open(*signer.public_key(), &repo).unwrap();
+
    let oid = radicle::git::Oid::from_str(HEAD).unwrap();
+
    let base = radicle::git::Oid::from_str(HEAD_1).unwrap();
+
    patches
+
        .create(
+
            "A new `hello word`",
+
            "change `hello world` in README to something else",
+
            MergeTarget::Delegates,
+
            base,
+
            oid,
+
            &[],
+
            &signer,
+
        )
+
        .unwrap();
+

+
    Context::new(Arc::new(profile))
+
}
+

+
/// Adds an authorized session to the Context::sessions HashMap.
+
pub async fn create_session(ctx: Context) {
+
    let issued_at = OffsetDateTime::now_utc();
+
    let mut sessions = ctx.sessions().write().await;
+
    sessions.insert(
+
        String::from("u9MGAkkfkMOv0uDDB2WeUHBT7HbsO2Dy"),
+
        auth::Session {
+
            status: auth::AuthState::Authorized,
+
            public_key: ctx.profile().public_key,
+
            issued_at,
+
            expires_at: issued_at
+
                .checked_add(auth::AUTHORIZED_SESSIONS_EXPIRATION)
+
                .unwrap(),
+
        },
+
    );
+
}
+

+
pub async fn get(app: &Router, path: impl ToString) -> Response {
+
    Response(
+
        app.clone()
+
            .oneshot(request(path, Method::GET, None, None))
+
            .await
+
            .unwrap(),
+
    )
+
}
+

+
pub async fn post(
+
    app: &Router,
+
    path: impl ToString,
+
    body: Option<Body>,
+
    auth: Option<String>,
+
) -> Response {
+
    Response(
+
        app.clone()
+
            .oneshot(request(path, Method::POST, body, auth))
+
            .await
+
            .unwrap(),
+
    )
+
}
+

+
pub async fn patch(
+
    app: &Router,
+
    path: impl ToString,
+
    body: Option<Body>,
+
    auth: Option<String>,
+
) -> Response {
+
    Response(
+
        app.clone()
+
            .oneshot(request(path, Method::PATCH, body, auth))
+
            .await
+
            .unwrap(),
+
    )
+
}
+

+
pub async fn put(
+
    app: &Router,
+
    path: impl ToString,
+
    body: Option<Body>,
+
    auth: Option<String>,
+
) -> Response {
+
    Response(
+
        app.clone()
+
            .oneshot(request(path, Method::PUT, body, auth))
+
            .await
+
            .unwrap(),
+
    )
+
}
+

+
fn request(
+
    path: impl ToString,
+
    method: Method,
+
    body: Option<Body>,
+
    auth: Option<String>,
+
) -> Request<Body> {
+
    let mut request = Request::builder()
+
        .method(method)
+
        .uri(path.to_string())
+
        .header("Content-Type", "application/json");
+
    if let Some(token) = auth {
+
        request = request.header("Authorization", format!("Bearer {token}"));
+
    }
+

+
    request.body(body.unwrap_or_else(Body::empty)).unwrap()
+
}
+

+
pub struct Response(axum::response::Response);
+

+
impl Response {
+
    pub async fn json(self) -> Value {
+
        let body = hyper::body::to_bytes(self.0.into_body()).await.unwrap();
+
        serde_json::from_slice(&body).unwrap()
+
    }
+

+
    pub fn status(&self) -> axum::http::StatusCode {
+
        self.0.status()
+
    }
+
}