| // 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(®istration_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( |
| (®istration_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((®istration_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", ®istration).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! {}); |
| }); |
| } |
| } |