blob: 279e2c45958b0503a58d4397e11a94c5847a96e5 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::{anyhow, Context, Result},
ffx_config::{self, ConfigLevel},
fidl_fuchsia_developer_ffx_ext::{RepositorySpec, RepositoryTarget},
percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS},
serde_json::Value,
std::collections::HashMap,
};
const CONFIG_KEY_REPOSITORIES: &str = "repository.repositories";
const CONFIG_KEY_REGISTRATIONS: &str = "repository.registrations";
const CONFIG_KEY_DEFAULT_REPOSITORY: &str = "repository.default";
const CONFIG_KEY_SERVER_MODE: &str = "repository.server.mode";
const CONFIG_KEY_SERVER_ENABLED: &str = "repository.server.enabled";
const CONFIG_KEY_SERVER_LISTEN: &str = "repository.server.listen";
const ESCAPE_SET: &AsciiSet = &CONTROLS.add(b'%').add(b'.');
// Try to figure out why the server is not running.
pub async fn determine_why_repository_server_is_not_running() -> anyhow::Error {
macro_rules! check {
($e:expr) => {
match $e {
Ok(value) => value,
Err(err) => {
return err;
}
}
};
}
if !check!(get_repository_server_enabled().await) {
return anyhow!(
"Server is disabled. It can be started with:\n\
$ ffx repository server start",
);
}
match check!(repository_listen_addr().await) {
Some(addr) => {
return anyhow!(
"Another process may be using {}. Try shutting it down and restarting the \
ffx daemon with:\n\
$ ffx repository server start",
addr,
);
}
None => {
return anyhow!(
"Server listening address is unspecified. You can fix this with:\n\
$ ffx config set repository.server.listen '[::]:8083'\n\
$ ffx doctor --restart-daemon",
);
}
}
}
/// Return the repository server mode.
pub async fn repository_server_mode() -> Result<String> {
if let Some(mode) = ffx_config::get(CONFIG_KEY_SERVER_MODE).await? {
Ok(mode)
} else {
Ok(String::new())
}
}
/// Return if the repository server is enabled.
pub async fn get_repository_server_enabled() -> Result<bool> {
Ok(ffx_config::get(CONFIG_KEY_SERVER_ENABLED).await?)
}
/// Sets if the repository server is enabled.
pub async fn set_repository_server_enabled(enabled: bool) -> Result<()> {
ffx_config::set((CONFIG_KEY_SERVER_ENABLED, ConfigLevel::User), enabled.into()).await
}
/// Return the repository server address.
pub async fn repository_listen_addr() -> Result<Option<std::net::SocketAddr>> {
if let Some(address) = ffx_config::get::<Option<String>, _>(CONFIG_KEY_SERVER_LISTEN).await? {
Ok(Some(
address
.parse::<std::net::SocketAddr>()
.with_context(|| format!("Failed to parse {}", CONFIG_KEY_SERVER_LISTEN))?,
))
} else {
Ok(None)
}
}
fn repository_query(repo_name: &str) -> String {
let repo_name = percent_encode(repo_name.as_bytes(), ESCAPE_SET);
format!("{}.{}", CONFIG_KEY_REPOSITORIES, repo_name)
}
fn registration_query(repo_name: &str, target_identifier: &str) -> String {
let repo_name = percent_encode(repo_name.as_bytes(), ESCAPE_SET);
let target_identifier = percent_encode(target_identifier.as_bytes(), ESCAPE_SET);
format!("{}.{}.{}", CONFIG_KEY_REGISTRATIONS, repo_name, target_identifier)
}
fn repository_registrations_query(repo_name: &str) -> String {
let repo_name = percent_encode(repo_name.as_bytes(), ESCAPE_SET);
format!("{}.{}", CONFIG_KEY_REGISTRATIONS, repo_name)
}
/// Return the default repository from the configuration if set.
pub async fn get_default_repository() -> Result<Option<String>> {
let config_default: Option<String> = ffx_config::get(CONFIG_KEY_DEFAULT_REPOSITORY).await?;
if config_default.is_some() {
return Ok(config_default);
} else {
let repos = get_repositories().await;
if repos.len() == 1 {
return Ok(repos.keys().next().map(|s| s.clone()));
} else {
Ok(None)
}
}
}
/// Sets the default repository from the config.
pub async fn set_default_repository(repo_name: &str) -> Result<()> {
ffx_config::set((CONFIG_KEY_DEFAULT_REPOSITORY, ConfigLevel::User), repo_name.into()).await
}
/// Unsets the default repository from the config.
pub async fn unset_default_repository() -> Result<()> {
ffx_config::remove((CONFIG_KEY_DEFAULT_REPOSITORY, ConfigLevel::User)).await
}
/// Get repository spec from config.
pub async fn get_repository(repo_name: &str) -> Result<Option<RepositorySpec>> {
if let Some(value) = ffx_config::get(&repository_query(repo_name)).await? {
Ok(serde_json::from_value(value)?)
} else {
Ok(None)
}
}
/// Read all the repositories from the config. This will log, but otherwise ignore invalid entries.
pub async fn get_repositories() -> HashMap<String, RepositorySpec> {
let value = match ffx_config::get::<Option<Value>, _>(CONFIG_KEY_REPOSITORIES).await {
Ok(Some(value)) => value,
Ok(None) => {
return HashMap::new();
}
Err(err) => {
log::warn!("failed to load repositories: {:#?}", err);
return HashMap::new();
}
};
let entries = match value {
Value::Object(entries) => entries,
_ => {
log::warn!("expected {} to be a map, not {}", CONFIG_KEY_REPOSITORIES, value);
return HashMap::new();
}
};
entries
.into_iter()
.filter_map(|(repo_name, entry)| {
let repo_name = match percent_decode_str(&repo_name).decode_utf8() {
Ok(repo_name) => repo_name.to_string(),
Err(err) => {
log::warn!("failed to decode repo name {:?}: {:#?}", repo_name, err);
return None;
}
};
// Parse the repository spec.
match serde_json::from_value(entry) {
Ok(repo_spec) => Some((repo_name, repo_spec)),
Err(err) => {
log::warn!("failed to parse repository {:#?}: {:?}", repo_name, err);
None
}
}
})
.collect()
}
/// Writes the repository named `repo_name` to the configuration.
pub async fn set_repository(repo_name: &str, repo_spec: &RepositorySpec) -> Result<()> {
let repo_spec = serde_json::to_value(repo_spec.clone())?;
ffx_config::set((&repository_query(repo_name), ConfigLevel::User), repo_spec).await
}
/// Removes the repository named `repo_name` from the configuration.
pub async fn remove_repository(repo_name: &str) -> Result<()> {
ffx_config::remove((&repository_query(repo_name), ConfigLevel::User)).await
}
/// Get the target registration from the config if exists.
pub async fn get_registration(
repo_name: &str,
target_identifier: &str,
) -> Result<Option<RepositoryTarget>> {
if let Some(value) = ffx_config::get(&registration_query(repo_name, target_identifier)).await? {
Ok(Some(serde_json::from_value(value)?))
} else {
Ok(None)
}
}
pub async fn get_registrations() -> HashMap<String, HashMap<String, RepositoryTarget>> {
let value = match ffx_config::get::<Option<Value>, _>(CONFIG_KEY_REGISTRATIONS).await {
Ok(Some(value)) => value,
Ok(None) => {
return HashMap::new();
}
Err(err) => {
log::warn!("failed to load registrations: {:#?}", err);
return HashMap::new();
}
};
let entries = match value {
Value::Object(entries) => entries,
_ => {
log::warn!("expected {} to be a map, not {}", CONFIG_KEY_REGISTRATIONS, value);
return HashMap::new();
}
};
entries
.into_iter()
.filter_map(|(repo_name, targets)| {
let repo_name = match percent_decode_str(&repo_name).decode_utf8() {
Ok(repo_name) => repo_name.to_string(),
Err(err) => {
log::warn!("unable to decode repo name {:?}: {:#?}", repo_name, err);
return None;
}
};
let targets = parse_target_registrations(&repo_name, targets);
Some((repo_name, targets))
})
.collect()
}
pub async fn get_repository_registrations(repo_name: &str) -> HashMap<String, RepositoryTarget> {
let targets = match ffx_config::get(&repository_registrations_query(repo_name)).await {
Ok(Some(targets)) => targets,
Ok(None) => {
return HashMap::new();
}
Err(err) => {
log::warn!("failed to load repository registrations: {:?} {:#?}", repo_name, err);
serde_json::Map::new().into()
}
};
parse_target_registrations(repo_name, targets)
}
fn parse_target_registrations(
repo_name: &str,
targets: serde_json::Value,
) -> HashMap<String, RepositoryTarget> {
let targets = match targets {
Value::Object(targets) => targets,
_ => {
log::warn!("repository {:?} targets should be a map, not {:?}", repo_name, targets);
return HashMap::new();
}
};
targets
.into_iter()
.filter_map(|(target_nodename, target_info)| {
let target_nodename = match percent_decode_str(&target_nodename).decode_utf8() {
Ok(target_nodename) => target_nodename.to_string(),
Err(err) => {
log::warn!(
"failed to decode target nodename: {:?}: {:#?}",
target_nodename,
err
);
return None;
}
};
match serde_json::from_value(target_info) {
Ok(target_info) => Some((target_nodename, target_info)),
Err(err) => {
log::warn!("failed to parse registration {:?}: {:?}", target_nodename, err);
None
}
}
})
.collect()
}
pub async fn set_registration(target_nodename: &str, target_info: &RepositoryTarget) -> Result<()> {
let json_target_info =
serde_json::to_value(&target_info).context("serializing RepositorySpec")?;
ffx_config::set(
(&registration_query(&target_info.repo_name, &target_nodename), ConfigLevel::User),
json_target_info,
)
.await
}
pub async fn remove_registration(repo_name: &str, target_identifier: &str) -> Result<()> {
ffx_config::remove((&registration_query(repo_name, target_identifier), ConfigLevel::User)).await
}
#[cfg(test)]
mod tests {
use {super::*, maplit::hashmap, serde_json::json, std::future::Future};
const CONFIG_KEY_ROOT: &str = "repository";
// FIXME(http://fxbug.dev/80740): Unfortunately ffx_config is global, and so each of these tests
// could step on each others ffx_config entries if run in parallel. To avoid this, we will:
//
// * use the `serial_test` crate to make sure each test runs sequentially
// * clear out the config keys before we run each test to make sure state isn't leaked across
// tests.
fn run_async_test<F: Future>(fut: F) -> F::Output {
fuchsia_async::TestExecutor::new().unwrap().run_singlethreaded(async move {
ffx_config::test_init().unwrap();
fut.await
})
}
#[test]
fn test_repository_query() {
assert_eq!(repository_query(""), format!("{}.", CONFIG_KEY_REPOSITORIES));
assert_eq!(repository_query("a-b"), format!("{}.a-b", CONFIG_KEY_REPOSITORIES));
assert_eq!(repository_query("a.b"), format!("{}.a%2Eb", CONFIG_KEY_REPOSITORIES));
assert_eq!(repository_query("a%b"), format!("{}.a%25b", CONFIG_KEY_REPOSITORIES));
}
#[test]
fn test_registration_query() {
assert_eq!(registration_query("", ""), format!("{}..", CONFIG_KEY_REGISTRATIONS));
assert_eq!(
registration_query("a-b", "c-d"),
format!("{}.a-b.c-d", CONFIG_KEY_REGISTRATIONS)
);
assert_eq!(
registration_query("a.b", "c.d"),
format!("{}.a%2Eb.c%2Ed", CONFIG_KEY_REGISTRATIONS)
);
assert_eq!(
registration_query("a%b", "c%d"),
format!("{}.a%25b.c%25d", CONFIG_KEY_REGISTRATIONS)
);
}
#[test]
fn test_repository_registrations_query() {
assert_eq!(repository_registrations_query(""), format!("{}.", CONFIG_KEY_REGISTRATIONS));
assert_eq!(
repository_registrations_query("a-b"),
format!("{}.a-b", CONFIG_KEY_REGISTRATIONS)
);
assert_eq!(
repository_registrations_query("a.b"),
format!("{}.a%2Eb", CONFIG_KEY_REGISTRATIONS)
);
assert_eq!(
repository_registrations_query("a%b"),
format!("{}.a%25b", CONFIG_KEY_REGISTRATIONS)
);
}
#[serial_test::serial]
#[test]
fn test_get_default_repository_with_only_one_configured() {
run_async_test(async {
ffx_config::set((CONFIG_KEY_ROOT, ConfigLevel::User), json!({})).await.unwrap();
// Initially there's no default.
assert_eq!(get_default_repository().await.unwrap(), None);
// Add the repository.
let repository = RepositorySpec::Pm { path: "foo/bar/baz".into() };
set_repository("repo", &repository).await.unwrap();
// The single configured repo should be returned as the default
assert_eq!(get_default_repository().await.unwrap(), Some(String::from("repo")));
// Add a second repository.
let repository = RepositorySpec::Pm { path: "foo/bar/baz2".into() };
set_repository("repo2", &repository).await.unwrap();
// The single configured repo should be returned as the default
assert_eq!(get_default_repository().await.unwrap(), None);
});
}
#[serial_test::serial]
#[test]
fn test_get_set_unset_default_repository() {
run_async_test(async {
ffx_config::set((CONFIG_KEY_ROOT, ConfigLevel::User), json!({})).await.unwrap();
// Initially there's no default.
assert_eq!(get_default_repository().await.unwrap(), None);
// Setting the default should write to the config.
set_default_repository("foo").await.unwrap();
assert_eq!(
ffx_config::get::<Value, _>(CONFIG_KEY_DEFAULT_REPOSITORY).await.unwrap(),
json!("foo"),
);
assert_eq!(get_default_repository().await.unwrap(), Some("foo".into()));
// We don't care if the repository has `.` in it.
set_default_repository("foo.bar").await.unwrap();
assert_eq!(
ffx_config::get::<Value, _>(CONFIG_KEY_DEFAULT_REPOSITORY).await.unwrap(),
json!("foo.bar"),
);
assert_eq!(get_default_repository().await.unwrap(), Some("foo.bar".into()));
// Unset removes the default repository from the config.
unset_default_repository().await.unwrap();
assert_eq!(
ffx_config::get::<Option<Value>, _>(CONFIG_KEY_DEFAULT_REPOSITORY).await.unwrap(),
None,
);
assert_eq!(get_default_repository().await.unwrap(), None);
// Unsetting the repo again returns an error.
assert!(unset_default_repository().await.is_err());
});
}
#[serial_test::serial]
#[test]
fn test_get_set_remove_repository() {
run_async_test(async {
ffx_config::set((CONFIG_KEY_REPOSITORIES, ConfigLevel::User), json!({})).await.unwrap();
// Initially the repositoy does not exist.
assert_eq!(get_repository("repo").await.unwrap(), None);
// Add the repository.
let repository = RepositorySpec::Pm { path: "foo/bar/baz".into() };
set_repository("repo", &repository).await.unwrap();
// Make sure we wrote to the config.
assert_eq!(
ffx_config::get::<Value, _>(CONFIG_KEY_REPOSITORIES).await.unwrap(),
json!({
"repo": {
"type": "pm",
"path": "foo/bar/baz",
}
}),
);
// Make sure we can get the repository.
assert_eq!(get_repository("repo").await.unwrap(), Some(repository));
// We can't get unknown repositories.
assert_eq!(get_repository("unknown").await.unwrap(), None);
// We can remove the repository.
remove_repository("repo").await.unwrap();
assert_eq!(
ffx_config::get::<Option<Value>, _>(CONFIG_KEY_REPOSITORIES).await.unwrap(),
None,
);
assert_eq!(get_repository("repo").await.unwrap(), None);
});
}
#[serial_test::serial]
#[test]
fn test_set_repository_encoding() {
run_async_test(async {
ffx_config::set((CONFIG_KEY_REPOSITORIES, ConfigLevel::User), json!({})).await.unwrap();
set_repository("repo.name", &RepositorySpec::Pm { path: "foo/bar/baz".into() })
.await
.unwrap();
set_repository("repo%name", &RepositorySpec::Pm { path: "foo/bar/baz".into() })
.await
.unwrap();
assert_eq!(
ffx_config::get::<Value, _>(CONFIG_KEY_REPOSITORIES).await.unwrap(),
json!({
"repo%2Ename": {
"type": "pm",
"path": "foo/bar/baz",
},
"repo%25name": {
"type": "pm",
"path": "foo/bar/baz",
}
}),
);
});
}
#[serial_test::serial]
#[test]
fn test_get_set_remove_registration() {
run_async_test(async {
ffx_config::set((CONFIG_KEY_REGISTRATIONS, ConfigLevel::User), json!({}))
.await
.unwrap();
// Initially the registration does not exist.
assert_eq!(get_registration("repo", "target").await.unwrap(), None);
// Add the registration.
let registration = RepositoryTarget {
repo_name: "repo".into(),
target_identifier: Some("target".into()),
aliases: vec![],
storage_type: None,
};
set_registration("target", &registration).await.unwrap();
// Make sure it was written to the config.
assert_eq!(
ffx_config::get::<Value, _>(CONFIG_KEY_REGISTRATIONS).await.unwrap(),
json!({
"repo": {
"target": {
"repo_name": "repo",
"target_identifier": "target",
"aliases": [],
"storage_type": (),
},
}
}),
);
// Make sure we can get the registration.
assert_eq!(get_registration("repo", "target").await.unwrap(), Some(registration));
// We can't get unknown registrations.
assert_eq!(get_registration("unkown", "target").await.unwrap(), None);
assert_eq!(get_registration("repo", "unknown").await.unwrap(), None);
// Remove the registration.
remove_registration("repo", "target").await.unwrap();
assert_eq!(get_registration("repo", "target").await.unwrap(), None);
});
}
#[serial_test::serial]
#[test]
fn test_set_registration_encoding() {
run_async_test(async {
ffx_config::set((CONFIG_KEY_REGISTRATIONS, ConfigLevel::User), json!({}))
.await
.unwrap();
set_registration(
"target.name",
&RepositoryTarget {
repo_name: "repo.name".into(),
target_identifier: Some("target.name".into()),
aliases: vec![],
storage_type: None,
},
)
.await
.unwrap();
set_registration(
"target%name",
&RepositoryTarget {
repo_name: "repo%name".into(),
target_identifier: Some("target%name".into()),
aliases: vec![],
storage_type: None,
},
)
.await
.unwrap();
assert_eq!(
ffx_config::get::<Value, _>(CONFIG_KEY_REGISTRATIONS).await.unwrap(),
json!({
"repo%2Ename": {
"target%2Ename": {
"repo_name": "repo.name",
"target_identifier": "target.name",
"aliases": [],
"storage_type": (),
},
},
"repo%25name": {
"target%25name": {
"repo_name": "repo%name",
"target_identifier": "target%name",
"aliases": [],
"storage_type": (),
},
}
}),
);
});
}
#[serial_test::serial]
#[test]
fn test_get_repositories_empty() {
run_async_test(async {
ffx_config::set((CONFIG_KEY_REPOSITORIES, ConfigLevel::User), json!({})).await.unwrap();
assert_eq!(get_repositories().await, hashmap! {});
});
}
#[serial_test::serial]
#[test]
fn test_get_repositories() {
run_async_test(async {
ffx_config::set(
(CONFIG_KEY_REPOSITORIES, ConfigLevel::User),
json!({
// Parse a normal repository.
"repo-name": {
"type": "pm",
"path": "foo/bar/baz",
},
// Parse encoded `repo.name`.
"repo%2Ename": {
"type": "pm",
"path": "foo/bar/baz",
},
// Parse encoded `repo%name`.
"repo%25name": {
"type": "pm",
"path": "foo/bar/baz",
},
}),
)
.await
.unwrap();
assert_eq!(
get_repositories().await,
hashmap! {
"repo-name".into() => RepositorySpec::Pm {
path: "foo/bar/baz".into(),
},
"repo.name".into() => RepositorySpec::Pm {
path: "foo/bar/baz".into(),
},
"repo%name".into() => RepositorySpec::Pm {
path: "foo/bar/baz".into(),
},
}
);
});
}
#[serial_test::serial]
#[test]
fn test_get_repositories_ignores_invalid_entries() {
run_async_test(async {
ffx_config::set(
(CONFIG_KEY_REPOSITORIES, ConfigLevel::User),
json!({
// Ignores invalid repositories.
"invalid-entries": {},
// Ignores invalid encoded repository names.
"invalid%aaencoding": {
"type": "pm",
"path": "repo/bar/baz",
},
}),
)
.await
.unwrap();
assert_eq!(get_repositories().await, hashmap! {});
});
}
#[serial_test::serial]
#[test]
fn test_get_registrations_empty() {
run_async_test(async {
ffx_config::set((CONFIG_KEY_REGISTRATIONS, ConfigLevel::User), json!({}))
.await
.unwrap();
assert_eq!(get_registrations().await, hashmap! {});
});
}
#[serial_test::serial]
#[test]
fn test_get_registrations() {
run_async_test(async {
ffx_config::set(
(CONFIG_KEY_REGISTRATIONS, ConfigLevel::User),
json!({
"repo1": {
"target1": {
"repo_name": "repo1",
"target_identifier": "target1",
"aliases": [],
"storage_type": (),
},
"target2": {
"repo_name": "repo1",
"target_identifier": "target2",
"aliases": [],
"storage_type": (),
},
// Ignores invalid targets.
"target3": {},
},
"repo2": {
"target1": {
"repo_name": "repo2",
"target_identifier": "target1",
"aliases": [],
"storage_type": (),
},
"target2": {
"repo_name": "repo2",
"target_identifier": "target2",
"aliases": [],
"storage_type": (),
},
},
}),
)
.await
.unwrap();
assert_eq!(
get_registrations().await,
hashmap! {
"repo1".into() => hashmap! {
"target1".into() => RepositoryTarget {
repo_name: "repo1".into(),
target_identifier: Some("target1".into()),
aliases: vec![],
storage_type: None,
},
"target2".into() => RepositoryTarget {
repo_name: "repo1".into(),
target_identifier: Some("target2".into()),
aliases: vec![],
storage_type: None,
},
},
"repo2".into() => hashmap! {
"target1".into() => RepositoryTarget {
repo_name: "repo2".into(),
target_identifier: Some("target1".into()),
aliases: vec![],
storage_type: None,
},
"target2".into() => RepositoryTarget {
repo_name: "repo2".into(),
target_identifier: Some("target2".into()),
aliases: vec![],
storage_type: None,
},
},
}
);
});
}
#[serial_test::serial]
#[test]
fn test_get_registrations_encoding() {
run_async_test(async {
ffx_config::set(
(CONFIG_KEY_REGISTRATIONS, ConfigLevel::User),
json!({
// Parse an encoded `repo.name`.
"repo%2Ename": {
// Parse an encoded `target.name`.
"target%2Ename": {
"repo_name": "repo.name",
"target_identifier": "target.name",
"aliases": [],
"storage_type": (),
},
// Parse an encoded `target%name`.
"target%25name": {
"repo_name": "repo.name",
"target_identifier": "target%name",
"aliases": [],
"storage_type": (),
},
},
// Parse encoded `foo%name`.
"repo%25name": {
"target-name": {
"repo_name": "repo%name",
"target_identifier": "target-name",
"aliases": [],
"storage_type": (),
},
},
}),
)
.await
.unwrap();
assert_eq!(
get_registrations().await,
hashmap! {
"repo.name".into() => hashmap! {
"target.name".into() => RepositoryTarget {
repo_name: "repo.name".into(),
target_identifier: Some("target.name".into()),
aliases: vec![],
storage_type: None,
},
"target%name".into() => RepositoryTarget {
repo_name: "repo.name".into(),
target_identifier: Some("target%name".into()),
aliases: vec![],
storage_type: None,
},
},
"repo%name".into() => hashmap! {
"target-name".into() => RepositoryTarget {
repo_name: "repo%name".into(),
target_identifier: Some("target-name".into()),
aliases: vec![],
storage_type: None,
},
},
}
);
});
}
#[serial_test::serial]
#[test]
fn test_get_registrations_invalid_entries() {
run_async_test(async {
ffx_config::set(
(CONFIG_KEY_REGISTRATIONS, ConfigLevel::User),
json!({
// Ignores empty repositories.
"empty-entries": {},
"repo-name": {
// Ignores invalid targets.
"invalid-target": {},
// Ignores invalid encoded target.
"invalid%aaencoding": {
"repo_name": "repo-name",
"target_identifier": "target-name",
"aliases": [],
"storage_type": (),
},
},
// Ignores invalid encoded repository names.
"invalid%aarepo": {
"target-name": {
"repo_name": "repo-name",
"target_identifier": "target-name",
"aliases": [],
"storage_type": (),
},
},
}),
)
.await
.unwrap();
assert_eq!(
get_registrations().await,
hashmap! {
"repo-name".into() => hashmap! {},
}
);
});
}
#[serial_test::serial]
#[test]
fn test_get_repository_registrations_empty() {
run_async_test(async {
ffx_config::set((CONFIG_KEY_REGISTRATIONS, ConfigLevel::User), json!({}))
.await
.unwrap();
assert_eq!(get_repository_registrations("repo").await, hashmap! {});
});
}
#[serial_test::serial]
#[test]
fn test_get_repository_registrations() {
run_async_test(async {
ffx_config::set(
(CONFIG_KEY_REGISTRATIONS, ConfigLevel::User),
json!({
"repo": {
"target1": {
"repo_name": "repo1",
"target_identifier": "target1",
"aliases": [],
"storage_type": (),
},
"target2": {
"repo_name": "repo1",
"target_identifier": "target2",
"aliases": [],
"storage_type": (),
},
// Ignores invalid targets.
"target3": {},
},
}),
)
.await
.unwrap();
assert_eq!(
get_repository_registrations("repo").await,
hashmap! {
"target1".into() => RepositoryTarget {
repo_name: "repo1".into(),
target_identifier: Some("target1".into()),
aliases: vec![],
storage_type: None,
},
"target2".into() => RepositoryTarget {
repo_name: "repo1".into(),
target_identifier: Some("target2".into()),
aliases: vec![],
storage_type: None,
},
},
);
// Getting an unknown repository gets nothing.
assert_eq!(get_repository_registrations("unknown").await, hashmap! {});
});
}
#[serial_test::serial]
#[test]
fn test_get_repository_registrations_encoding() {
run_async_test(async {
ffx_config::set(
(CONFIG_KEY_REGISTRATIONS, ConfigLevel::User),
json!({
// Parse an encoded `repo.name`.
"repo%2Ename": {
// Parse an encoded `target.name`.
"target%2Ename": {
"repo_name": "repo.name",
"target_identifier": "target.name",
"aliases": [],
"storage_type": (),
},
// Parse an encoded `target%name`.
"target%25name": {
"repo_name": "repo.name",
"target_identifier": "target%name",
"aliases": [],
"storage_type": (),
},
},
}),
)
.await
.unwrap();
assert_eq!(
get_repository_registrations("repo.name").await,
hashmap! {
"target.name".into() => RepositoryTarget {
repo_name: "repo.name".into(),
target_identifier: Some("target.name".into()),
aliases: vec![],
storage_type: None,
},
"target%name".into() => RepositoryTarget {
repo_name: "repo.name".into(),
target_identifier: Some("target%name".into()),
aliases: vec![],
storage_type: None,
},
},
);
});
}
#[serial_test::serial]
#[test]
fn test_get_repository_registrations_invalid_entries() {
run_async_test(async {
ffx_config::set(
(CONFIG_KEY_REGISTRATIONS, ConfigLevel::User),
json!({
// Ignores empty repositories.
"empty-entries": {},
"repo-name": {
// Ignores invalid targets.
"invalid-target": {},
// Ignores invalid encoded target.
"invalid%aaencoding": {
"repo_name": "repo-name",
"target_identifier": "target-name",
"aliases": [],
"storage_type": (),
},
},
// Ignores invalid encoded repository names.
"invalid%aarepo": {
"target-name": {
"repo_name": "repo-name",
"target_identifier": "target-name",
"aliases": [],
"storage_type": (),
},
},
}),
)
.await
.unwrap();
assert_eq!(get_repository_registrations("empty-entries").await, hashmap! {});
assert_eq!(get_repository_registrations("repo-name").await, hashmap! {});
});
}
}