httpd: Add patch update endpoint
Signed-off-by: Sebastian Martinez <me@sebastinez.dev>
This commit is contained in:
parent
dd97ac8384
commit
f2d31aa9c0
|
|
@ -12,8 +12,7 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_json::json;
|
||||
use tower_http::set_header::SetResponseHeaderLayer;
|
||||
|
||||
use radicle::cob::{issue, patch};
|
||||
use radicle::cob::{thread, ActorId, Tag};
|
||||
use radicle::cob::{issue, patch, thread, ActorId, Tag};
|
||||
use radicle::identity::Id;
|
||||
use radicle::node::NodeId;
|
||||
use radicle::storage::git::paths;
|
||||
|
|
@ -61,7 +60,10 @@ pub fn router(ctx: Context) -> Router {
|
|||
"/projects/:project/patches",
|
||||
post(patch_create_handler).get(patches_handler),
|
||||
)
|
||||
.route("/projects/:project/patches/:id", get(patch_handler))
|
||||
.route(
|
||||
"/projects/:project/patches/:id",
|
||||
get(patch_handler).patch(patch_update_handler),
|
||||
)
|
||||
.with_state(ctx)
|
||||
}
|
||||
|
||||
|
|
@ -595,6 +597,81 @@ async fn patch_create_handler(
|
|||
Json(json!({ "success": true, "id": patch.id.to_string() })),
|
||||
))
|
||||
}
|
||||
/// Update an patch.
|
||||
/// `PATCH /projects/:project/patches/:id`
|
||||
async fn patch_update_handler(
|
||||
State(ctx): State<Context>,
|
||||
AuthBearer(token): AuthBearer,
|
||||
Path((project, patch_id)): Path<(Id, Oid)>,
|
||||
Json(action): Json<patch::Action>,
|
||||
) -> impl IntoResponse {
|
||||
ctx.sessions
|
||||
.write()
|
||||
.await
|
||||
.get(&token)
|
||||
.ok_or(Error::Auth("Unauthorized"))?;
|
||||
let storage = &ctx.profile.storage;
|
||||
let signer = ctx
|
||||
.profile
|
||||
.signer()
|
||||
.map_err(|_| Error::Auth("Unauthorized"))?;
|
||||
let repo = storage.repository(project)?;
|
||||
let mut patches = patch::Patches::open(&repo)?;
|
||||
let mut patch = patches.get_mut(&patch_id.into())?;
|
||||
match action {
|
||||
patch::Action::Edit {
|
||||
title,
|
||||
description,
|
||||
target,
|
||||
} => {
|
||||
patch.edit(title, description, target, &signer)?;
|
||||
}
|
||||
patch::Action::Tag { add, remove } => {
|
||||
patch.tag(add, remove, &signer)?;
|
||||
}
|
||||
patch::Action::Revision {
|
||||
description,
|
||||
base,
|
||||
oid,
|
||||
} => {
|
||||
patch.update(description, base, oid, &signer)?;
|
||||
}
|
||||
patch::Action::Redact { .. } => {
|
||||
todo!()
|
||||
}
|
||||
patch::Action::Review {
|
||||
revision,
|
||||
comment,
|
||||
verdict,
|
||||
inline,
|
||||
} => {
|
||||
patch.review(revision, verdict, comment, inline, &signer)?;
|
||||
}
|
||||
patch::Action::Merge { revision, commit } => {
|
||||
patch.merge(revision, commit, &signer)?;
|
||||
}
|
||||
patch::Action::Thread { action, revision } => match action {
|
||||
thread::Action::Comment { body, reply_to } => {
|
||||
if let Some(reply_to) = reply_to {
|
||||
patch.comment(revision, body, Some(reply_to), &signer)?;
|
||||
} else {
|
||||
patch.thread(revision, body, &signer)?;
|
||||
}
|
||||
}
|
||||
thread::Action::Edit { .. } => {
|
||||
todo!();
|
||||
}
|
||||
thread::Action::Redact { .. } => {
|
||||
todo!();
|
||||
}
|
||||
thread::Action::React { .. } => {
|
||||
todo!();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok::<_, Error>(Json(json!({ "success": true })))
|
||||
}
|
||||
|
||||
/// Get project patches list.
|
||||
/// `GET /projects/:project/patches`
|
||||
|
|
@ -643,16 +720,12 @@ mod routes {
|
|||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::test::{
|
||||
self, get, patch, post, CONTRIBUTOR_ISSUE_ID, CONTRIBUTOR_PUB_KEY, CONTRIBUTOR_RID, HEAD,
|
||||
INITIAL_COMMIT, ISSUE_COMMENT_ID, ISSUE_DISCUSSION_ID, ISSUE_ID, PARENT, PATCH_ID, RID,
|
||||
SESSION_ID, TIMESTAMP,
|
||||
};
|
||||
use crate::test::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_projects_root() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(&app, "/projects").await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
@ -663,7 +736,7 @@ mod routes {
|
|||
"name": "hello-world",
|
||||
"description": "Rad repository for tests",
|
||||
"defaultBranch": "master",
|
||||
"delegates": ["did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"],
|
||||
"delegates": [DID],
|
||||
"head": HEAD,
|
||||
"patches": {
|
||||
"proposed": 1,
|
||||
|
|
@ -683,7 +756,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(&app, format!("/projects/{RID}")).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
@ -693,7 +766,7 @@ mod routes {
|
|||
"name": "hello-world",
|
||||
"description": "Rad repository for tests",
|
||||
"defaultBranch": "master",
|
||||
"delegates": ["did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"],
|
||||
"delegates": [DID],
|
||||
"head": HEAD,
|
||||
"patches": {
|
||||
"proposed": 1,
|
||||
|
|
@ -712,7 +785,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_commits_root() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(&app, format!("/projects/{RID}/commits")).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
@ -945,7 +1018,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_commits() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(&app, format!("/projects/{RID}/commits/{HEAD}")).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
@ -1047,7 +1120,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_tree() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(&app, format!("/projects/{RID}/tree/{HEAD}/")).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
@ -1132,7 +1205,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_remotes_root() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(&app, format!("/projects/{RID}/remotes")).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
@ -1153,7 +1226,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_remotes() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{RID}/remotes/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"),
|
||||
|
|
@ -1176,7 +1249,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_blob() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(&app, format!("/projects/{RID}/blob/{HEAD}/README")).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
@ -1208,7 +1281,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_readme() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(&app, format!("/projects/{RID}/readme/{INITIAL_COMMIT}")).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
@ -1240,7 +1313,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_diff() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{RID}/diff/{INITIAL_COMMIT}/{HEAD}"),
|
||||
|
|
@ -1320,7 +1393,7 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_issues_root() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let app = super::router(seed(tmp.path()));
|
||||
let response = get(&app, format!("/projects/{RID}/issues")).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
|
@ -1330,7 +1403,7 @@ mod routes {
|
|||
{
|
||||
"id": ISSUE_ID,
|
||||
"author": {
|
||||
"id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
|
||||
"id": DID
|
||||
},
|
||||
"title": "Issue #1",
|
||||
"state": {
|
||||
|
|
@ -1341,7 +1414,7 @@ mod routes {
|
|||
{
|
||||
"id": ISSUE_ID,
|
||||
"author": {
|
||||
"id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
|
||||
"id": DID
|
||||
},
|
||||
"body": "Change 'hello world' to 'hello everyone'",
|
||||
"reactions": [],
|
||||
|
|
@ -1360,10 +1433,10 @@ mod routes {
|
|||
const CREATED_ISSUE_ID: &str = "b457364fbe2ef0eac69a835a087f60ee13ccb367";
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test::contributor(tmp.path());
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
|
||||
test::create_session(ctx).await;
|
||||
create_session(ctx).await;
|
||||
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"title": "Issue #2",
|
||||
|
|
@ -1398,7 +1471,7 @@ mod routes {
|
|||
json!({
|
||||
"id": CREATED_ISSUE_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_PUB_KEY,
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"assignees": [],
|
||||
"title": "Issue #2",
|
||||
|
|
@ -1408,7 +1481,7 @@ mod routes {
|
|||
"discussion": [{
|
||||
"id": CREATED_ISSUE_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_PUB_KEY,
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"body": "Change 'hello world' to 'hello everyone'",
|
||||
"reactions": [],
|
||||
|
|
@ -1425,10 +1498,10 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_issues_comment() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test::contributor(tmp.path());
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
|
||||
test::create_session(ctx).await;
|
||||
create_session(ctx).await;
|
||||
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"type": "thread",
|
||||
|
|
@ -1461,7 +1534,7 @@ mod routes {
|
|||
json!({
|
||||
"id": CONTRIBUTOR_ISSUE_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_PUB_KEY,
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"assignees": [],
|
||||
"title": "Issue #1",
|
||||
|
|
@ -1472,7 +1545,7 @@ mod routes {
|
|||
{
|
||||
"id": ISSUE_DISCUSSION_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_PUB_KEY,
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"body": "Change 'hello world' to 'hello everyone'",
|
||||
"reactions": [],
|
||||
|
|
@ -1482,7 +1555,7 @@ mod routes {
|
|||
{
|
||||
"id": "9685b141c2e939c3d60f8ca34f8c7bf01a609af1",
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_PUB_KEY,
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"body": "This is first-level comment",
|
||||
"reactions": [],
|
||||
|
|
@ -1498,10 +1571,10 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_issues_reply() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test::contributor(tmp.path());
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
|
||||
test::create_session(ctx).await;
|
||||
create_session(ctx).await;
|
||||
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"type": "thread",
|
||||
|
|
@ -1536,7 +1609,7 @@ mod routes {
|
|||
json!({
|
||||
"id": CONTRIBUTOR_ISSUE_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_PUB_KEY,
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"assignees": [],
|
||||
"title": "Issue #1",
|
||||
|
|
@ -1547,7 +1620,7 @@ mod routes {
|
|||
{
|
||||
"id": ISSUE_DISCUSSION_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_PUB_KEY,
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"body": "Change 'hello world' to 'hello everyone'",
|
||||
"reactions": [],
|
||||
|
|
@ -1557,7 +1630,7 @@ mod routes {
|
|||
{
|
||||
"id": ISSUE_COMMENT_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_PUB_KEY,
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"body": "This is a reply to the first comment",
|
||||
"reactions": [],
|
||||
|
|
@ -1573,32 +1646,33 @@ mod routes {
|
|||
#[tokio::test]
|
||||
async fn test_projects_patches() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let app = super::router(test::seed(tmp.path()));
|
||||
let response = get(&app, format!("/projects/{RID}/patches")).await;
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
let response = get(&app, format!("/projects/{CONTRIBUTOR_RID}/patches")).await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
response.json().await,
|
||||
json!([
|
||||
{
|
||||
"id": PATCH_ID,
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"author": {
|
||||
"id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
|
||||
"id": CONTRIBUTOR_DID
|
||||
},
|
||||
"title": "A new `hello word`",
|
||||
"title": "A new `hello world`",
|
||||
"description": "change `hello world` in README to something else",
|
||||
"state": { "status": "proposed" },
|
||||
"target": "delegates",
|
||||
"tags": [],
|
||||
"revisions": [
|
||||
{
|
||||
"id": PATCH_ID,
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"description": "",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"merges": [],
|
||||
"discussions": [],
|
||||
"timestamp": 1671125284,
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [],
|
||||
}
|
||||
],
|
||||
|
|
@ -1606,31 +1680,35 @@ mod routes {
|
|||
])
|
||||
);
|
||||
|
||||
let response = get(&app, format!("/projects/{RID}/patches/{PATCH_ID}")).await;
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
response.json().await,
|
||||
json!(
|
||||
{
|
||||
"id": PATCH_ID,
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"author": {
|
||||
"id": "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
|
||||
"id": CONTRIBUTOR_DID
|
||||
},
|
||||
"title": "A new `hello word`",
|
||||
"title": "A new `hello world`",
|
||||
"description": "change `hello world` in README to something else",
|
||||
"state": { "status": "proposed" },
|
||||
"target": "delegates",
|
||||
"tags": [],
|
||||
"revisions": [
|
||||
{
|
||||
"id": PATCH_ID,
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"description": "",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"merges": [],
|
||||
"discussions": [],
|
||||
"timestamp": 1671125284,
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [],
|
||||
}
|
||||
],
|
||||
|
|
@ -1644,10 +1722,10 @@ mod routes {
|
|||
const CREATED_PATCH_ID: &str = "f69641cba6d7df2c22844d7f39225b5cda54d363";
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = test::contributor(tmp.path());
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
|
||||
test::create_session(ctx).await;
|
||||
create_session(ctx).await;
|
||||
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"title": "Update README",
|
||||
|
|
@ -1690,7 +1768,7 @@ mod routes {
|
|||
{
|
||||
"id": CREATED_PATCH_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_PUB_KEY
|
||||
"id": CONTRIBUTOR_DID
|
||||
},
|
||||
"title": "Update README",
|
||||
"description": "Do some changes to README",
|
||||
|
|
@ -1705,7 +1783,7 @@ mod routes {
|
|||
"oid": HEAD,
|
||||
"merges": [],
|
||||
"discussions": [],
|
||||
"timestamp": 1671125284,
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [],
|
||||
}
|
||||
],
|
||||
|
|
@ -1713,4 +1791,443 @@ mod routes {
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_projects_patches_tag() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
create_session(ctx).await;
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"type": "tag",
|
||||
"add": ["bug","design"],
|
||||
"remove": []
|
||||
}))
|
||||
.unwrap();
|
||||
let response = patch(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
Some(Body::from(body)),
|
||||
Some(SESSION_ID.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
response.json().await,
|
||||
json!({
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"title": "A new `hello world`",
|
||||
"description": "change `hello world` in README to something else",
|
||||
"state": { "status": "proposed" },
|
||||
"target": "delegates",
|
||||
"tags": [
|
||||
"bug",
|
||||
"design"
|
||||
],
|
||||
"revisions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"description": "",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"merges": [],
|
||||
"discussions": [],
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_projects_patches_revisions() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
create_session(ctx).await;
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"type": "revision",
|
||||
"description": "This is a new revision",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
}))
|
||||
.unwrap();
|
||||
let response = patch(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
Some(Body::from(body)),
|
||||
Some(SESSION_ID.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
response.json().await,
|
||||
json!({
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"title": "A new `hello world`",
|
||||
"description": "change `hello world` in README to something else",
|
||||
"state": { "status": "proposed" },
|
||||
"target": "delegates",
|
||||
"tags": [],
|
||||
"revisions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"description": "",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"merges": [],
|
||||
"discussions": [],
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [],
|
||||
},
|
||||
{
|
||||
"id": "bdc0364b51a3346653a795b73332eb792283be37",
|
||||
"description": "This is a new revision",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"merges": [],
|
||||
"discussions": [],
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [],
|
||||
}
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_projects_patches_edit() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
create_session(ctx).await;
|
||||
let body = serde_json::to_vec(&json!({
|
||||
"type": "edit",
|
||||
"title": "This is a updated title",
|
||||
"description": "Let's write some description",
|
||||
"target": "delegates",
|
||||
}))
|
||||
.unwrap();
|
||||
let response = patch(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
Some(Body::from(body)),
|
||||
Some(SESSION_ID.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
response.json().await,
|
||||
json!({
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"title": "This is a updated title",
|
||||
"description": "Let's write some description",
|
||||
"state": { "status": "proposed" },
|
||||
"target": "delegates",
|
||||
"tags": [],
|
||||
"revisions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"description": "",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"merges": [],
|
||||
"discussions": [],
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_projects_patches_discussions() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
create_session(ctx).await;
|
||||
let thread_body = serde_json::to_vec(&json!({
|
||||
"type": "thread",
|
||||
"revision": CONTRIBUTOR_PATCH_ID,
|
||||
"action": {
|
||||
"type": "comment",
|
||||
"body": "This is a root level comment"
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
let response = patch(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
Some(Body::from(thread_body)),
|
||||
Some(SESSION_ID.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let reply_body = serde_json::to_vec(&json!({
|
||||
"type": "thread",
|
||||
"revision": CONTRIBUTOR_PATCH_ID,
|
||||
"action": {
|
||||
"type": "comment",
|
||||
"body": "This is a root level comment",
|
||||
"replyTo": CONTRIBUTOR_COMMENT_1,
|
||||
}
|
||||
}))
|
||||
.unwrap();
|
||||
let response = patch(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
Some(Body::from(reply_body)),
|
||||
Some(SESSION_ID.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
response.json().await,
|
||||
json!({
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"title": "A new `hello world`",
|
||||
"description": "change `hello world` in README to something else",
|
||||
"state": { "status": "proposed" },
|
||||
"target": "delegates",
|
||||
"tags": [],
|
||||
"revisions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"description": "",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"merges": [],
|
||||
"discussions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_COMMENT_1,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"body": "This is a root level comment",
|
||||
"reactions": [],
|
||||
"timestamp": TIMESTAMP,
|
||||
"replyTo": null,
|
||||
},
|
||||
{
|
||||
"id": CONTRIBUTOR_COMMENT_2,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"body": "This is a root level comment",
|
||||
"reactions": [],
|
||||
"timestamp": TIMESTAMP,
|
||||
"replyTo": CONTRIBUTOR_COMMENT_1,
|
||||
},
|
||||
],
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_projects_patches_reviews() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
create_session(ctx).await;
|
||||
let thread_body = serde_json::to_vec(&json!({
|
||||
"type": "review",
|
||||
"revision": CONTRIBUTOR_PATCH_ID,
|
||||
"comment": "A small review",
|
||||
"verdict": "accept",
|
||||
"inline": [
|
||||
{
|
||||
"location": {
|
||||
"blob": HEAD,
|
||||
"commit": HEAD,
|
||||
"lines": {
|
||||
"start": 1,
|
||||
"end": 3,
|
||||
},
|
||||
},
|
||||
"comment": "This is a comment on line 1",
|
||||
"timestamp": TIMESTAMP,
|
||||
}
|
||||
]
|
||||
}))
|
||||
.unwrap();
|
||||
let response = patch(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
Some(Body::from(thread_body)),
|
||||
Some(SESSION_ID.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
response.json().await,
|
||||
json!({
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"title": "A new `hello world`",
|
||||
"description": "change `hello world` in README to something else",
|
||||
"state": { "status": "proposed" },
|
||||
"target": "delegates",
|
||||
"tags": [],
|
||||
"revisions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"description": "",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"merges": [],
|
||||
"discussions": [],
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [
|
||||
[
|
||||
CONTRIBUTOR_NID,
|
||||
{
|
||||
"verdict": "accept",
|
||||
"comment": "A small review",
|
||||
"inline": [
|
||||
{
|
||||
"location": {
|
||||
"blob": HEAD,
|
||||
"commit": HEAD,
|
||||
"lines": {
|
||||
"start": 1,
|
||||
"end": 3,
|
||||
},
|
||||
},
|
||||
"comment": "This is a comment on line 1",
|
||||
"timestamp": TIMESTAMP,
|
||||
}
|
||||
],
|
||||
"timestamp": TIMESTAMP,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_projects_patches_merges() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let ctx = contributor(tmp.path());
|
||||
let app = super::router(ctx.to_owned());
|
||||
create_session(ctx).await;
|
||||
let thread_body = serde_json::to_vec(&json!({
|
||||
"type": "merge",
|
||||
"revision": CONTRIBUTOR_PATCH_ID,
|
||||
"commit": PARENT,
|
||||
}))
|
||||
.unwrap();
|
||||
let response = patch(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
Some(Body::from(thread_body)),
|
||||
Some(SESSION_ID.to_string()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
let response = get(
|
||||
&app,
|
||||
format!("/projects/{CONTRIBUTOR_RID}/patches/{CONTRIBUTOR_PATCH_ID}"),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
response.json().await,
|
||||
json!({
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"author": {
|
||||
"id": CONTRIBUTOR_DID,
|
||||
},
|
||||
"title": "A new `hello world`",
|
||||
"description": "change `hello world` in README to something else",
|
||||
"state": { "status": "proposed" },
|
||||
"target": "delegates",
|
||||
"tags": [],
|
||||
"revisions": [
|
||||
{
|
||||
"id": CONTRIBUTOR_PATCH_ID,
|
||||
"description": "",
|
||||
"base": PARENT,
|
||||
"oid": HEAD,
|
||||
"merges": [
|
||||
{
|
||||
"node": CONTRIBUTOR_NID,
|
||||
"commit": PARENT,
|
||||
"timestamp": TIMESTAMP,
|
||||
},
|
||||
],
|
||||
"discussions": [],
|
||||
"timestamp": TIMESTAMP,
|
||||
"reviews": [],
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,15 +27,19 @@ pub const RID: &str = "rad:z4FucBZHZMCsxTyQE1dfE2YR59Qbp";
|
|||
pub const HEAD: &str = "e8c676b9e3b42308dc9d218b70faa5408f8e58ca";
|
||||
pub const PARENT: &str = "ee8d6a29304623a78ebfa5eeed5af674d0e58f83";
|
||||
pub const INITIAL_COMMIT: &str = "f604ce9fd5b7cc77b7609beda45ea8760bee78f7";
|
||||
pub const PATCH_ID: &str = "a6afa179631163169bf9fb0921229317ec3950af";
|
||||
pub const DID: &str = "did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi";
|
||||
pub const ISSUE_ID: &str = "5ad77fa3f476beed9a26f49b2b3b844e61bef792";
|
||||
pub const ISSUE_DISCUSSION_ID: &str = "f1dff128a22e8183a23516dd9812e72e80914c92";
|
||||
pub const ISSUE_COMMENT_ID: &str = "845218041bf9eb8155bfa4aaa8f0c91ce18e5c13";
|
||||
pub const SESSION_ID: &str = "u9MGAkkfkMOv0uDDB2WeUHBT7HbsO2Dy";
|
||||
pub const TIMESTAMP: u64 = 1671125284;
|
||||
pub const CONTRIBUTOR_RID: &str = "rad:z4XaCmN3jLSeiMvW15YTDpNbDHFhG";
|
||||
pub const CONTRIBUTOR_PUB_KEY: &str = "did:key:z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8";
|
||||
pub const CONTRIBUTOR_DID: &str = "did:key:z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8";
|
||||
pub const CONTRIBUTOR_NID: &str = "z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8";
|
||||
pub const CONTRIBUTOR_ISSUE_ID: &str = "f1dff128a22e8183a23516dd9812e72e80914c92";
|
||||
pub const CONTRIBUTOR_PATCH_ID: &str = "4b7ea91da1f551bc5ea5d2057125d7ba42d451cb";
|
||||
pub const CONTRIBUTOR_COMMENT_1: &str = "8f6e72c20da0e6ee2af004f059ced23d409a285a";
|
||||
pub const CONTRIBUTOR_COMMENT_2: &str = "c88ce78b17a705f389e72ec5c80da7360a675e51";
|
||||
|
||||
const PASSWORD: &str = "radicle";
|
||||
|
||||
|
|
@ -178,7 +182,7 @@ fn seed_with_signer<G: Signer>(dir: &Path, profile: radicle::Profile, signer: &G
|
|||
let base = radicle::git::Oid::from_str(PARENT).unwrap();
|
||||
let _ = patches
|
||||
.create(
|
||||
"A new `hello word`",
|
||||
"A new `hello world`",
|
||||
"change `hello world` in README to something else",
|
||||
MergeTarget::Delegates,
|
||||
base,
|
||||
|
|
|
|||
|
|
@ -519,9 +519,10 @@ impl Serialize for Review {
|
|||
where
|
||||
S: serde::ser::Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("Review", 3)?;
|
||||
let mut state = serializer.serialize_struct("Review", 4)?;
|
||||
state.serialize_field("verdict", &self.verdict())?;
|
||||
state.serialize_field("comment", &self.comment())?;
|
||||
state.serialize_field("inline", &self.inline().collect::<Vec<_>>())?;
|
||||
state.serialize_field("timestamp", &self.timestamp())?;
|
||||
state.end()
|
||||
}
|
||||
|
|
@ -560,6 +561,10 @@ impl Review {
|
|||
self.verdict.get().as_ref().copied()
|
||||
}
|
||||
|
||||
pub fn inline(&self) -> impl Iterator<Item = &CodeComment> {
|
||||
self.inline.iter().map(|m| m.get())
|
||||
}
|
||||
|
||||
pub fn comment(&self) -> Option<&str> {
|
||||
self.comment.get().as_ref().map(|m| m.get().as_str())
|
||||
}
|
||||
|
|
@ -721,6 +726,16 @@ impl<'a, 'g> PatchMut<'a, 'g> {
|
|||
self.transaction("Edit", signer, |tx| tx.edit(title, description, target))
|
||||
}
|
||||
|
||||
/// Create a thread on a patch revision.
|
||||
pub fn thread<G: Signer, S: ToString>(
|
||||
&mut self,
|
||||
revision: RevisionId,
|
||||
body: S,
|
||||
signer: &G,
|
||||
) -> Result<CommentId, Error> {
|
||||
self.transaction("Create thread", signer, |tx| tx.thread(revision, body))
|
||||
}
|
||||
|
||||
/// Comment on a patch revision.
|
||||
pub fn comment<G: Signer, S: ToString>(
|
||||
&mut self,
|
||||
|
|
|
|||
Loading…
Reference in New Issue