-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CLI command to configure TOS for an organization
Closes #8778
- Loading branch information
1 parent
64f3638
commit aa9f644
Showing
7 changed files
with
301 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
use std::{collections::HashMap, path::PathBuf}; | ||
|
||
use libparsec::{OrganizationID, ParsecAddr}; | ||
|
||
crate::clap_parser_with_shared_opts_builder!( | ||
#[with = addr, token, organization] | ||
pub struct Args { | ||
/// If set, will remove TOS for the provided organization. | ||
#[arg(long)] | ||
remove: bool, | ||
/// Read the TOS configuration from a JSON file. | ||
#[arg(long)] | ||
from_json: Option<PathBuf>, | ||
/// List of locale and url for TOS configuration | ||
/// Each arguments should be in the form of `{locale}={url}` | ||
raw: Vec<String>, | ||
} | ||
); | ||
|
||
pub async fn main(args: Args) -> anyhow::Result<()> { | ||
let Args { | ||
organization, | ||
token, | ||
addr, | ||
raw, | ||
remove, | ||
from_json, | ||
} = args; | ||
log::trace!("Configure TOS for organization {organization} (addr={addr})"); | ||
|
||
let raw_data = from_json.map(std::fs::read_to_string).transpose()?; | ||
|
||
let req = if let Some(ref raw_data) = raw_data { | ||
let localized_tos_url = serde_json::from_str::<HashMap<_, _>>(raw_data)?; | ||
TosReq::set_tos(localized_tos_url) | ||
} else if remove { | ||
TosReq::to_remove() | ||
} else { | ||
let localized_tos_url = raw | ||
.iter() | ||
.map(|arg| { | ||
arg.split_once('=') | ||
.ok_or_else(|| anyhow::anyhow!("Missing '=<URL>' in argument ('{arg}')")) | ||
}) | ||
.collect::<anyhow::Result<HashMap<_, _>>>()?; | ||
TosReq::set_tos(localized_tos_url) | ||
}; | ||
|
||
config_tos_for_org_req(&addr, &token, &organization, req).await | ||
} | ||
|
||
#[derive(serde::Serialize)] | ||
pub(crate) struct TosReq<'a> { | ||
tos: Option<HashMap<&'a str, &'a str>>, | ||
} | ||
|
||
impl<'a> TosReq<'a> { | ||
pub(crate) fn to_remove() -> Self { | ||
Self { tos: None } | ||
} | ||
|
||
pub(crate) fn set_tos(tos: HashMap<&'a str, &'a str>) -> Self { | ||
Self { tos: Some(tos) } | ||
} | ||
} | ||
|
||
pub(crate) async fn config_tos_for_org_req<'a>( | ||
addr: &ParsecAddr, | ||
token: &'a str, | ||
organization: &OrganizationID, | ||
tos_req: TosReq<'a>, | ||
) -> anyhow::Result<()> { | ||
let url = addr.to_http_url(Some(&format!( | ||
"/administration/organizations/{organization}" | ||
))); | ||
let client = libparsec_client_connection::build_client()?; | ||
let rep = client | ||
.patch(url) | ||
.json(&tos_req) | ||
.bearer_auth(token) | ||
.send() | ||
.await?; | ||
|
||
match rep.status() { | ||
reqwest::StatusCode::OK => Ok(()), | ||
reqwest::StatusCode::NOT_FOUND => { | ||
Err(anyhow::anyhow!("Organization {organization} not found")) | ||
} | ||
code => Err(anyhow::anyhow!("Unexpected HTTP status code {code}")), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,17 @@ | ||
pub mod config; | ||
pub mod list; | ||
|
||
#[derive(clap::Subcommand)] | ||
pub enum Group { | ||
// List available Terms of Service | ||
/// List available Terms of Service of an organization | ||
List(list::Args), | ||
/// Configure TOS for an organization | ||
Config(config::Args), | ||
} | ||
|
||
pub async fn dispatch_command(command: Group) -> anyhow::Result<()> { | ||
match command { | ||
Group::List(args) => list::main(args).await, | ||
Group::Config(args) => config::main(args).await, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
use std::collections::HashMap; | ||
|
||
use libparsec::{tmp_path, ClientGetTosError, TmpPath}; | ||
|
||
use crate::{ | ||
commands::tos::config::{config_tos_for_org_req, TosReq}, | ||
integration_tests::bootstrap_cli_test, | ||
testenv_utils::{TestOrganization, DEFAULT_ADMINISTRATION_TOKEN}, | ||
utils::start_client, | ||
}; | ||
|
||
#[rstest::rstest] | ||
#[tokio::test] | ||
async fn test_set_tos_from_arg(tmp_path: TmpPath) { | ||
let (addr, TestOrganization { alice, .. }, organization) = | ||
bootstrap_cli_test(&tmp_path).await.unwrap(); | ||
|
||
crate::assert_cmd_success!( | ||
"tos", | ||
"config", | ||
"--organization", | ||
organization.as_ref(), | ||
"--token", | ||
DEFAULT_ADMINISTRATION_TOKEN, | ||
"--addr", | ||
&addr.to_string(), | ||
"fr_fr=http://example.com/tos", | ||
"en_CA=http://example.com/en/tos" | ||
) | ||
.stdout(predicates::str::is_empty()); | ||
|
||
let client = start_client(alice).await.unwrap(); | ||
|
||
let tos = client.get_tos().await.unwrap(); | ||
|
||
assert_eq!( | ||
tos.per_locale_urls, | ||
HashMap::from_iter([ | ||
("fr_fr".into(), "http://example.com/tos".into()), | ||
("en_CA".into(), "http://example.com/en/tos".into()), | ||
]) | ||
); | ||
} | ||
|
||
#[rstest::rstest] | ||
#[tokio::test] | ||
async fn test_set_tos_from_file(tmp_path: TmpPath) { | ||
let (addr, TestOrganization { alice, .. }, organization) = | ||
bootstrap_cli_test(&tmp_path).await.unwrap(); | ||
|
||
let expected_tos = HashMap::from_iter([ | ||
("fr_fr".into(), "http://example.com/tos".into()), | ||
("en_CA".into(), "http://example.com/en/tos".into()), | ||
]); | ||
let tos_file = tmp_path.join("tos.json"); | ||
tokio::fs::write(&tos_file, serde_json::to_vec(&expected_tos).unwrap()) | ||
.await | ||
.unwrap(); | ||
|
||
crate::assert_cmd_success!( | ||
"tos", | ||
"config", | ||
"--organization", | ||
organization.as_ref(), | ||
"--token", | ||
DEFAULT_ADMINISTRATION_TOKEN, | ||
"--addr", | ||
&addr.to_string(), | ||
"--from-json", | ||
&tos_file.display().to_string() | ||
) | ||
.stdout(predicates::str::is_empty()); | ||
|
||
let client = start_client(alice).await.unwrap(); | ||
|
||
let tos = client.get_tos().await.unwrap(); | ||
|
||
assert_eq!(tos.per_locale_urls, expected_tos); | ||
} | ||
|
||
#[rstest::rstest] | ||
#[tokio::test] | ||
async fn test_remove_tos(tmp_path: TmpPath) { | ||
let (addr, TestOrganization { alice, .. }, organization) = | ||
bootstrap_cli_test(&tmp_path).await.unwrap(); | ||
|
||
config_tos_for_org_req( | ||
&addr, | ||
DEFAULT_ADMINISTRATION_TOKEN, | ||
&organization, | ||
TosReq::set_tos(HashMap::from_iter([("fr_fr", "http://parsec.local/tos")])), | ||
) | ||
.await | ||
.unwrap(); | ||
|
||
let client = start_client(alice).await.unwrap(); | ||
let tos = client.get_tos().await.unwrap(); | ||
|
||
assert!(!tos.per_locale_urls.is_empty()); | ||
|
||
crate::assert_cmd_success!( | ||
"tos", | ||
"config", | ||
"--organization", | ||
organization.as_ref(), | ||
"--token", | ||
DEFAULT_ADMINISTRATION_TOKEN, | ||
"--addr", | ||
&addr.to_string(), | ||
"--remove" | ||
) | ||
.stdout(predicates::str::is_empty()); | ||
|
||
let err = client.get_tos().await.unwrap_err(); | ||
|
||
assert!(matches!(err, ClientGetTosError::NoTos)); | ||
} | ||
|
||
#[tokio::test] | ||
async fn test_invalid_tos_arg() { | ||
crate::assert_cmd_failure!( | ||
"tos", | ||
"config", | ||
"--organization=foobar", | ||
"--token=123456", | ||
"--addr=parsec3://example.com", | ||
"fr_fr" | ||
) | ||
.stderr(predicates::str::contains( | ||
"Missing '=<URL>' in argument ('fr_fr')", | ||
)); | ||
} | ||
|
||
#[rstest::rstest] | ||
#[tokio::test] | ||
async fn test_org_not_found(tmp_path: TmpPath) { | ||
let (addr, _, _) = bootstrap_cli_test(&tmp_path).await.unwrap(); | ||
|
||
crate::assert_cmd_failure!( | ||
"tos", | ||
"config", | ||
"--organization=foobar", | ||
"--token", | ||
DEFAULT_ADMINISTRATION_TOKEN, | ||
"--addr", | ||
&addr.to_string(), | ||
"--remove" | ||
) | ||
.stderr(predicates::str::contains("Organization foobar not found")); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
mod config; | ||
mod list; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add CLI Command to configure TOS for an organization |