#[macro_use]
mod utils;
use cd::{
model::{
Change, ChangeContent, CommitDetail, CommitMessage, Entry, EntryContent, Project, Query,
Repository, Revision,
},
ContentService, ProjectService, RepoService,
};
use centraldogma as cd;
use std::pin::Pin;
use anyhow::{ensure, Context, Result};
use futures::future::{Future, FutureExt};
use serde_json::json;
struct TestContext {
client: cd::Client,
project: Project,
repo: Repository,
}
async fn run_test<T>(test: T)
where
for<'a> T: FnOnce(&'a mut TestContext) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>,
{
let mut ctx = setup().await.expect("Failed to setup for test");
let result = test(&mut ctx).await;
teardown(ctx).await.expect("Failed to teardown test setup");
result.unwrap();
}
async fn setup() -> Result<TestContext> {
let client = cd::Client::new("http://localhost:36462", None)
.await
.context("Failed to create client")?;
let projects = client
.list_projects()
.await
.context("Failed to list projects")?;
assert_eq!(0, projects.len());
let prj_name = "TestProject";
let project = client
.create_project(prj_name)
.await
.context("Failed to create new project")?;
let repo_name = "TestRepo";
let repo = client
.project(prj_name)
.create_repo(repo_name)
.await
.context("Failed to create new repository")?;
Ok(TestContext {
client,
project,
repo,
})
}
async fn teardown(ctx: TestContext) -> Result<()> {
ctx.client
.project(&ctx.project.name)
.remove_repo(&ctx.repo.name)
.await
.context("Failed to remove the repo")?;
ctx.client
.project(&ctx.project.name)
.purge_repo(&ctx.repo.name)
.await
.context("Failed to remove the repo")?;
ctx.client
.remove_project(&ctx.project.name)
.await
.context("Failed to remove the project")?;
ctx.client
.purge_project(&ctx.project.name)
.await
.context("Failed to purge the project")?;
Ok(())
}
fn t<'a>(ctx: &'a mut TestContext) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
async move {
let r = ctx.client.repo(&ctx.project.name, &ctx.repo.name);
let push_result = {
let commit_msg = CommitMessage {
summary: "New file".to_string(),
detail: Some(CommitDetail::Plaintext("detail".to_string())),
};
let changes = vec![Change {
path: "/a.json".to_string(),
content: ChangeContent::UpsertJson(json!({
"test_key": "test_value"
})),
}, Change {
path: "/folder/b.txt".to_string(),
content: ChangeContent::UpsertText("text value".to_string()),
}];
r.push(
Revision::HEAD,
commit_msg,
changes,
)
.await
.context(here!("Failed to push file"))?
};
{
let file: Entry = r.get_file(
push_result.revision,
&Query::of_json("/a.json").unwrap(),
)
.await
.context(here!("Failed to fetch file content"))?;
println!("File: {:?}", &file);
ensure!(
matches!(file.content, EntryContent::Json(json) if json == json!({"test_key": "test_value"})),
here!("Expect same json content")
);
}
{
let file_list = r.list_files(
Revision::HEAD,
""
)
.await
.context(here!("Failed to list files"))?;
println!("File list: {:?}", &file_list);
ensure!(
file_list.len() == 3,
here!("Wrong number of file entry returned")
);
}
{
let file: Entry = r.get_file(
push_result.revision,
&Query::of_json_path("/a.json", vec!["test_key".to_owned()]).unwrap(),
)
.await
.context(here!("Failed to fetch file content"))?;
println!("File: {:?}", &file);
ensure!(
matches!(file.content, EntryContent::Json(json) if json == json!("test_value")),
here!("Expect same json content")
);
}
{
let commits = r.get_history(
Revision::INIT,
Revision::HEAD,
"/**",
None
)
.await
.context(here!("Failed to get history"))?;
println!("History: {:?}", &commits);
}
{
let commits = r.get_history(
Revision::DEFAULT,
Revision::DEFAULT,
"/**",
None
)
.await
.context(here!("Failed to get history"))?;
println!("History: {:?}", &commits);
}
{
let entries = r.get_files(
push_result.revision,
"a*"
)
.await
.context(here!("Failed to fetch multiple files"))?;
ensure!(entries.len() == 1, here!("wrong number of entries returned"));
let entries = r.get_files(
push_result.revision,
"*"
)
.await
.context(here!("Failed to fetch multiple files"))?;
ensure!(entries.len() == 3, here!("wrong number of entries returned"));
println!("Entries: {:?}", &entries);
let exist = entries.iter().any(|e| {
e.path == "/folder/b.txt" && matches!(&e.content, EntryContent::Text(s) if s == "text value\n")
});
ensure!(exist, here!("Expected value not found"));
}
{
let commit_msg = CommitMessage {
summary: "Update a.json".to_string(),
detail: None,
};
let changes = vec![Change {
path: "/a.json".to_string(),
content: ChangeContent::ApplyJsonPatch(json!([
{"op": "replace", "path": "/test_key", "value": "updated_value"},
{"op": "add", "path": "/new_key", "value": ["new_array_item1", "new_array_item2"]}
])),
}];
r.push(
Revision::HEAD,
commit_msg,
changes,
)
.await
.context(here!("Failed to push file"))?;
let diff = r.get_diff(
Revision::from(1),
Revision::HEAD,
&Query::of_json("/a.json").unwrap(),
)
.await
.context(here!("Failed to get diff"))?;
println!("Diff: {:?}", diff);
ensure!(diff.path == "/a.json", here!("Diff path incorrect"));
let expected_json = json!({
"new_key": ["new_array_item1", "new_array_item2"],
"test_key": "updated_value"
});
ensure!(
matches!(diff.content, ChangeContent::UpsertJson(json) if json == expected_json),
here!("Diff content incorrect")
);
}
{
let diffs = r.get_diffs(
Revision::from(1),
Revision::HEAD,
"*"
)
.await
.context(here!("Failed to get diff"))?;
ensure!(diffs.len() == 2, here!("Expect 2 diffs"));
}
Ok(())
}
.boxed()
}
#[cfg(test)]
#[tokio::test]
async fn test_content() {
run_test(t).await;
}