mod handlers;
mod safe_data;
use sn_client::QueriedDataReplicas;
use super::{files::FileInfo, Safe};
pub use super::{ContentType, DataType, SafeUrl, VersionHash, XorUrlBase};
use crate::{Error, Result};
use log::{debug, info};
pub use safe_data::SafeData;
pub type Range = Option<(Option<u64>, Option<u64>)>;
const INDIRECTION_LIMIT: usize = 10;
impl Safe {
pub async fn parse_and_resolve_url(&self, url: &str) -> Result<SafeUrl> {
let safe_url = SafeUrl::from_url(url)?;
let orig_path = safe_url.path_decoded()?;
let mut resolution_chain = self
.fully_resolve_url(
safe_url, None, false, None, false, )
.await?;
let safe_data = resolution_chain
.pop()
.ok_or_else(|| Error::ContentNotFound(format!("Failed to resolve {url}")))?;
let mut new_safe_url = SafeUrl::from_url(&safe_data.xorurl())?;
new_safe_url.set_path(&orig_path);
Ok(new_safe_url)
}
pub async fn fetch(&self, url: &str, range: Range) -> Result<SafeData> {
let safe_url = SafeUrl::from_url(url)?;
info!("URL parsed successfully, fetching: {url}");
let mut resolution_chain = self
.fully_resolve_url(safe_url, None, true, range, true)
.await?;
resolution_chain
.pop()
.ok_or_else(|| Error::ContentNotFound(format!("Failed to resolve {url}")))
}
pub async fn inspect(&self, url: &str) -> Result<Vec<SafeData>> {
let safe_url = SafeUrl::from_url(url)?;
info!("URL parsed successfully, inspecting: {url}");
self.fully_resolve_url(safe_url, None, false, None, true)
.await
}
pub async fn check_replicas(
&self,
url: &str,
replicas: &[usize],
) -> Result<Vec<QueriedDataReplicas>> {
let mut resolution_chain = self.inspect(url).await?;
let content = resolution_chain
.pop()
.ok_or_else(|| Error::ContentNotFound(format!("Failed to resolve {url}")))?;
let content_safeurl = SafeUrl::from_xorurl(&content.xorurl())?;
self.fetch_data_replicas(&content_safeurl, replicas).await
}
async fn fully_resolve_url(
&self,
input_url: SafeUrl,
attached_metadata: Option<FileInfo>,
retrieve_data: bool,
range: Range,
resolve_path: bool,
) -> Result<Vec<SafeData>> {
debug!(
"Fetching URL: {} with content of type: {:?}, data type: {:?}",
input_url,
input_url.content_type(),
input_url.data_type()
);
let mut indirections_limit = INDIRECTION_LIMIT;
let mut safe_data_vec = vec![];
let mut next_step = Some(input_url);
let mut metadata = attached_metadata;
while let Some(next_url) = next_step {
let safe_data = self
.resolve_url(next_url, metadata, retrieve_data, range, resolve_path)
.await?;
next_step = safe_data.resolves_into();
metadata = safe_data.metadata();
safe_data_vec.push(safe_data);
if indirections_limit == 0 {
return Err(Error::ContentError(format!("The maximum number of indirections ({INDIRECTION_LIMIT}) was reached when trying to resolve the URL provided")));
}
indirections_limit -= 1;
}
Ok(safe_data_vec)
}
async fn resolve_url(
&self,
input_url: SafeUrl,
attached_metadata: Option<FileInfo>,
retrieve_data: bool,
range: Range,
resolve_path: bool,
) -> Result<SafeData> {
debug!(
"Resolving URL: {}, of content type: {:?}, and data type: {:?}, address {:?}",
input_url.to_xorurl_string(),
input_url.content_type(),
input_url.data_type(),
input_url.address()
);
match input_url.content_type() {
ContentType::FilesContainer => {
self.resolve_file_container(input_url, resolve_path).await
}
ContentType::NrsMapContainer => self.resolve_nrs_map_container(input_url).await,
ContentType::Multimap => self.resolve_multimap(input_url, retrieve_data).await,
ContentType::Raw => {
self.resolve_raw(input_url, attached_metadata, retrieve_data, range)
.await
}
ContentType::MediaType(media_type_str) => {
self.resolve_mediatype(
input_url,
attached_metadata,
retrieve_data,
range,
media_type_str,
)
.await
}
ContentType::Wallet { .. } => self.resolve_multimap(input_url, retrieve_data).await,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
app::files,
app::test_helpers::{new_safe_instance, random_nrs_name, TestDataFilesContainer},
SafeUrl,
};
use sn_interface::types::DataAddress;
use anyhow::{anyhow, bail, Context, Result};
use bytes::Bytes;
use std::io::Read;
#[tokio::test]
async fn test_fetch_files_container() -> Result<()> {
let safe = new_safe_instance().await?;
let (fc_xorurl, _, original_files_map) = safe
.files_container_create_from("./testdata/", None, true, false)
.await?;
let safe_url = SafeUrl::from_url(&fc_xorurl)?;
let content = safe.fetch(&fc_xorurl, None).await?;
let (version0, _) = safe
.files_container_get(&fc_xorurl)
.await?
.ok_or_else(|| anyhow!("files container was unexpectedly empty"))?;
match content.clone() {
SafeData::FilesContainer {
xorurl,
xorname,
type_tag,
version,
files_map,
data_type,
metadata,
resolves_into,
resolved_from,
} => {
assert_eq!(xorurl, fc_xorurl.clone());
assert_eq!(xorname, safe_url.xorname());
assert_eq!(type_tag, files::FILES_CONTAINER_TYPE_TAG);
assert_eq!(version, Some(version0));
assert_eq!(files_map, original_files_map);
assert_eq!(data_type, DataType::Register);
assert!(metadata.is_none()); assert!(resolves_into.is_none()); assert_eq!(resolved_from, fc_xorurl.clone());
}
_ => bail!("Invalid SafeData type! Expected SafeData::FileContainer!"),
}
let mut safe_url_with_path = safe_url.clone();
safe_url_with_path.set_path("/subfolder/subexists.md");
assert_eq!(safe_url_with_path.path(), "/subfolder/subexists.md");
assert_eq!(safe_url_with_path.xorname(), safe_url.xorname());
assert_eq!(safe_url_with_path.type_tag(), safe_url.type_tag());
assert_eq!(safe_url_with_path.content_type(), safe_url.content_type());
let inspected_content = safe.inspect(&fc_xorurl).await?;
assert_eq!(inspected_content.len(), 1);
assert_eq!(content, inspected_content[0]);
Ok(())
}
#[tokio::test]
async fn test_fetch_resolvable_container() -> Result<()> {
let safe = new_safe_instance().await?;
let (xorurl, _, the_files_map) = safe
.files_container_create_from("./testdata/", None, true, false)
.await?;
let _ = safe.fetch(&xorurl, None).await?;
let (version0, _) = safe
.files_container_get(&xorurl)
.await?
.ok_or_else(|| anyhow!("files container was unexpectedly empty"))?;
let mut safe_url = SafeUrl::from_url(&xorurl)?;
safe_url.set_content_version(Some(version0));
let site_name = random_nrs_name();
let _ = safe.nrs_add(&site_name, &safe_url).await?;
let nrs_url = format!("safe://{site_name}");
let content = safe.fetch(&nrs_url, None).await?;
safe_url.set_sub_names("")?;
let xorurl_without_subname = safe_url.to_string();
match &content {
SafeData::FilesContainer {
xorurl,
xorname,
type_tag,
version,
files_map,
data_type,
metadata,
resolves_into,
resolved_from,
} => {
assert_eq!(*xorurl, xorurl_without_subname);
assert_eq!(*xorname, safe_url.xorname());
assert_eq!(*type_tag, 1_100);
assert_eq!(*version, Some(version0));
assert_eq!(*data_type, DataType::Register);
assert_eq!(*files_map, the_files_map);
assert!(metadata.is_none());
assert!(resolves_into.is_none());
assert_eq!(resolved_from, &safe_url.to_string());
}
_ => {
bail!("FilesContainer was not returned".to_string())
}
}
let inspected_content = safe.inspect(&nrs_url).await?;
assert_eq!(inspected_content.len(), 2);
assert_eq!(content, inspected_content[1]);
Ok(())
}
#[tokio::test]
async fn test_fetch_resolvable_map_data() -> Result<()> {
let safe = new_safe_instance().await?;
let (xorurl, _, _the_files_map) = safe
.files_container_create_from("./testdata/", None, true, false)
.await?;
let _ = safe.fetch(&xorurl, None).await?;
let (version0, _) = safe
.files_container_get(&xorurl)
.await?
.ok_or_else(|| anyhow!("files container was unexpectedly empty"))?;
let mut safe_url = SafeUrl::from_url(&xorurl)?;
safe_url.set_content_version(Some(version0));
let files_container_url = safe_url;
let site_name = random_nrs_name();
let (nrs_resolution_url, did_create) =
safe.nrs_add(&site_name, &files_container_url).await?;
assert!(did_create);
let nrs_url = format!("safe://{site_name}");
let content = safe.fetch(&nrs_url, None).await?;
match &content {
SafeData::FilesContainer {
xorurl,
resolved_from,
resolves_into,
..
} => {
assert_eq!(*xorurl, files_container_url.to_string());
assert_eq!(*resolved_from, files_container_url.to_string());
assert!(resolves_into.is_none());
}
_ => {
bail!("FilesContainer was not returned".to_string());
}
}
let inspected_content = safe.inspect(&nrs_url).await?;
assert_eq!(inspected_content.len(), 2);
assert_eq!(&content, &inspected_content[1]);
match &inspected_content[0] {
SafeData::NrsEntry {
xorurl,
public_name,
data_type,
resolves_into,
resolved_from,
version,
} => {
assert_eq!(*xorurl, files_container_url.to_xorurl_string());
assert_eq!(*public_name, nrs_resolution_url.public_name());
assert_eq!(*data_type, nrs_resolution_url.data_type());
assert_eq!(*resolves_into, files_container_url);
assert_eq!(*resolved_from, nrs_url.to_string());
assert_eq!(*version, None);
}
_ => {
bail!("NrsEntry was not returned".to_string());
}
}
Ok(())
}
#[tokio::test]
async fn test_fetch_public_file() -> Result<()> {
let safe = new_safe_instance().await?;
let data = Bytes::from("Something super immutable");
let xorurl = safe.store_bytes(data.clone(), Some("text/plain")).await?;
let safe_url = SafeUrl::from_url(&xorurl)?;
let content = safe.fetch(&xorurl, None).await?;
assert!(
content
== SafeData::PublicFile {
xorurl: xorurl.clone(),
xorname: safe_url.xorname(),
data: data.clone(),
resolved_from: xorurl.clone(),
media_type: Some("text/plain".to_string()),
metadata: None,
}
);
let inspected_content = safe.inspect(&xorurl).await?;
assert!(
inspected_content[0]
== SafeData::PublicFile {
xorurl: xorurl.clone(),
xorname: safe_url.xorname(),
data: Bytes::new(),
resolved_from: xorurl,
media_type: Some("text/plain".to_string()),
metadata: None,
}
);
Ok(())
}
#[tokio::test]
async fn test_fetch_public_file_from_nrs_url() -> Result<()> {
let safe = new_safe_instance().await?;
let data = Bytes::from("Something super immutable");
let xorurl = safe.store_bytes(data.clone(), Some("text/plain")).await?;
let safe_url = SafeUrl::from_url(&xorurl)?;
let site_name = random_nrs_name();
let public_name = format!("file.{site_name}");
safe.nrs_add(&public_name, &safe_url).await?;
let content = safe.fetch(&format!("safe://{public_name}"), None).await?;
assert!(
content
== SafeData::PublicFile {
xorurl: xorurl.clone(),
xorname: safe_url.xorname(),
data: data.clone(),
resolved_from: xorurl.clone(),
media_type: Some("text/plain".to_string()),
metadata: None,
}
);
let inspected_content = safe.inspect(&xorurl).await?;
assert!(
inspected_content[0]
== SafeData::PublicFile {
xorurl: xorurl.clone(),
xorname: safe_url.xorname(),
data: Bytes::new(),
resolved_from: xorurl,
media_type: Some("text/plain".to_string()),
metadata: None,
}
);
Ok(())
}
#[tokio::test]
async fn test_fetch_range_public_file() -> Result<()> {
let safe = new_safe_instance().await?;
let saved_data = Bytes::from("Something super immutable");
let size = saved_data.len();
let xorurl = safe
.store_bytes(saved_data.clone(), Some("text/plain"))
.await?;
let fetch_first_half = Some((None, Some(size as u64 / 2)));
let content = safe.fetch(&xorurl, fetch_first_half).await?;
if let SafeData::PublicFile { data, .. } = content {
assert_eq!(data, saved_data.slice(0..size / 2));
} else {
bail!("Content fetched is not a PublicFile: {:?}", content);
}
let fetch_second_half = Some((Some(size as u64 / 2), None));
let content = safe.fetch(&xorurl, fetch_second_half).await?;
if let SafeData::PublicFile { data, .. } = content {
assert_eq!(data, saved_data[size / 2..]);
Ok(())
} else {
Err(anyhow!(
"Content fetched is not a PublicFile: {:?}",
content
))
}
}
#[tokio::test]
async fn test_fetch_range_from_files_container() -> Result<()> {
use std::fs::File;
let safe = new_safe_instance().await?;
let (xorurl, _, _files_map) = safe
.files_container_create_from("./testdata/", None, true, false)
.await?;
let _ = safe.fetch(&xorurl, None).await?;
let (version0, _) = safe
.files_container_get(&xorurl)
.await?
.ok_or_else(|| anyhow!("files container was unexpectedly empty"))?;
let mut safe_url = SafeUrl::from_url(&xorurl)?;
safe_url.set_content_version(Some(version0));
let site_name = random_nrs_name();
let _ = safe.nrs_add(&site_name, &safe_url).await?;
let nrs_url = format!("safe://{site_name}/test.md");
let mut file = File::open("./testdata/test.md")
.context("Failed to open local file: ./testdata/test.md".to_string())?;
let mut file_data = Vec::new();
file.read_to_end(&mut file_data)
.context("Failed to read local file: ./testdata/test.md".to_string())?;
let file_size = file_data.len();
let content = safe.fetch(&nrs_url, None).await?;
if let SafeData::PublicFile { data, .. } = &content {
assert_eq!(data.clone(), file_data.clone());
} else {
bail!("Content fetched is not a PublicFile: {:?}", content);
}
let fetch_first_half = Some((Some(0), Some(file_size as u64 / 2)));
let content = safe.fetch(&nrs_url, fetch_first_half).await?;
if let SafeData::PublicFile { data, .. } = content {
assert_eq!(data, file_data[0..file_size / 2]);
} else {
bail!("Content fetched is not a PublicFile: {:?}", content);
}
let fetch_second_half = Some((Some(file_size as u64 / 2), Some(file_size as u64)));
let content = safe.fetch(&nrs_url, fetch_second_half).await?;
if let SafeData::PublicFile { data, .. } = content {
assert_eq!(data, file_data[file_size / 2..]);
} else {
bail!("Content fetched is not a PublicFile: {:?}", content);
}
Ok(())
}
#[tokio::test]
async fn test_fetch_unsupported_with_media_type() -> Result<()> {
let safe = new_safe_instance().await?;
let xorname = xor_name::rand::random();
let type_tag = 575_756_443;
let xorurl = SafeUrl::new(
DataAddress::register(xorname, type_tag),
None,
type_tag,
ContentType::MediaType("text/html".to_string()),
None,
None,
None,
None,
None,
)?
.encode(XorUrlBase::Base32z);
match safe.fetch(&xorurl, None).await {
Ok(c) => {
bail!("Unxpected fetched content: {:?}", c)
}
Err(Error::ContentError(msg)) => {
assert_eq!(msg, "Data type 'Register' not supported yet".to_string())
}
other => bail!("Error returned is not the expected one: {:?}", other),
};
match safe.inspect(&xorurl).await {
Ok(c) => Err(anyhow!("Unxpected fetched content: {:?}", c)),
Err(Error::ContentError(msg)) => {
assert_eq!(msg, "Data type 'Register' not supported yet".to_string());
Ok(())
}
other => Err(anyhow!(
"Error returned is not the expected one: {:?}",
other
)),
}
}
#[tokio::test]
async fn test_fetch_file_with_path() -> Result<()> {
let safe = new_safe_instance().await?;
let data = Bytes::from("Something super immutable");
let xorurl = safe.store_bytes(data.clone(), None).await?;
let mut safe_url = SafeUrl::from_url(&xorurl)?;
let path = "/some_relative_filepath";
safe_url.set_path(path);
match safe.fetch(&safe_url.to_string(), None).await {
Ok(c) => {
bail!("Unxpected fetched content: {:?}", c)
}
Err(Error::ContentError(msg)) => assert_eq!(
msg,
format!("Cannot get relative path of Immutable Data \"{path}\"")
),
other => bail!("Error returned is not the expected one: {:?}", other),
};
let xorurl = safe.store_bytes(data.clone(), Some("text/plain")).await?;
let mut safe_url = SafeUrl::from_url(&xorurl)?;
safe_url.set_path("/some_relative_filepath");
let url_with_path = safe_url.to_string();
match safe.fetch(&url_with_path, None).await {
Ok(c) => Err(anyhow!("Unxpected fetched content: {:?}", c)),
Err(Error::ContentError(msg)) => {
assert_eq!(
msg,
format!("Cannot get relative path of Immutable Data \"{path}\"")
);
Ok(())
}
other => Err(anyhow!(
"Error returned is not the expected one: {:?}",
other
)),
}
}
#[tokio::test]
async fn test_fetch_should_resolve_nrs_map_container_when_xorurl_is_used_and_topname_has_link(
) -> Result<()> {
let site_name = random_nrs_name();
let safe = new_safe_instance().await?;
let files_container = TestDataFilesContainer::get_container([]).await?;
let nrs_url = safe.nrs_create(&site_name).await?;
let nrs_container_url = SafeUrl::from_url(&nrs_url.to_xorurl_string())?;
safe.nrs_associate(&site_name, &files_container.url).await?;
let content = safe.fetch(&nrs_container_url.to_string(), None).await?;
match &content {
SafeData::NrsMapContainer {
data_type,
nrs_map,
type_tag,
xorurl,
xorname,
} => {
assert_eq!(*data_type, nrs_container_url.data_type());
assert_eq!(nrs_map.map.len(), 1);
assert_eq!(*type_tag, nrs_container_url.type_tag());
assert_eq!(*xorurl, nrs_container_url.to_xorurl_string());
assert_eq!(*xorname, nrs_container_url.xorname());
}
_ => {
bail!("NrsMapContainer was not returned".to_string());
}
}
Ok(())
}
#[tokio::test]
async fn test_fetch_should_resolve_nrs_map_container_when_xorurl_is_used_and_topname_has_no_link(
) -> Result<()> {
let site_name = random_nrs_name();
let safe = new_safe_instance().await?;
let files_container = TestDataFilesContainer::get_container(["/testdata/test.md"]).await?;
let nrs_url = safe.nrs_create(&site_name).await?;
let nrs_container_url = SafeUrl::from_url(&nrs_url.to_xorurl_string())?;
safe.nrs_associate(
&format!("a.{site_name}"),
&files_container["/testdata/test.md"],
)
.await?;
let content = safe.fetch(&nrs_container_url.to_string(), None).await?;
match &content {
SafeData::NrsMapContainer {
data_type,
nrs_map,
type_tag,
xorurl,
xorname,
} => {
assert_eq!(*data_type, nrs_container_url.data_type());
assert_eq!(nrs_map.map.len(), 1);
assert_eq!(*type_tag, nrs_container_url.type_tag());
assert_eq!(*xorurl, nrs_container_url.to_xorurl_string());
assert_eq!(*xorname, nrs_container_url.xorname());
}
_ => {
bail!("NrsMapContainer was not returned".to_string());
}
}
Ok(())
}
#[tokio::test]
async fn test_fetch_should_resolve_nrs_map_container_when_nrsurl_is_used_and_topname_has_no_link(
) -> Result<()> {
let site_name = random_nrs_name();
let safe = new_safe_instance().await?;
let files_container = TestDataFilesContainer::get_container(["/testdata/test.md"]).await?;
let nrs_url = safe.nrs_create(&site_name).await?;
let nrs_container_url = SafeUrl::from_url(&nrs_url.to_xorurl_string())?;
safe.nrs_associate(
&format!("a.{site_name}"),
&files_container["/testdata/test.md"],
)
.await?;
let content = safe.fetch(&nrs_url.to_string(), None).await?;
match &content {
SafeData::NrsMapContainer {
data_type,
nrs_map,
type_tag,
xorurl,
xorname,
} => {
assert_eq!(*data_type, nrs_container_url.data_type());
assert_eq!(nrs_map.map.len(), 1);
assert_eq!(*type_tag, nrs_container_url.type_tag());
assert_eq!(*xorurl, nrs_container_url.to_xorurl_string());
assert_eq!(*xorname, nrs_container_url.xorname());
}
_ => {
bail!("NrsMapContainer was not returned".to_string());
}
}
Ok(())
}
#[tokio::test]
async fn test_fetch_should_resolve_linked_content_when_nrsurl_is_used_and_topname_has_a_link(
) -> Result<()> {
let site_name = random_nrs_name();
let safe = new_safe_instance().await?;
let files_container = TestDataFilesContainer::get_container(["/testdata/test.md"]).await?;
let nrs_url = safe.nrs_create(&site_name).await?;
safe.nrs_associate(&site_name, &files_container.url).await?;
safe.nrs_associate(
&format!("a.{site_name}"),
&files_container["/testdata/test.md"],
)
.await?;
let content = safe.fetch(&nrs_url.to_string(), None).await?;
match &content {
SafeData::FilesContainer {
xorurl,
resolved_from,
resolves_into,
..
} => {
assert_eq!(*xorurl, files_container.url.to_string());
assert_eq!(*resolved_from, files_container.url.to_string());
assert!(resolves_into.is_none());
}
_ => {
bail!("FilesContainer was not returned".to_string());
}
}
Ok(())
}
#[tokio::test]
async fn test_fetch_should_resolve_subname_to_specific_version() -> Result<()> {
let site_name = random_nrs_name();
let safe = new_safe_instance().await?;
let files_container = TestDataFilesContainer::get_container([
"/testdata/test.md",
"/testdata/another.md",
"/testdata/noextension",
])
.await?;
safe.nrs_create(&site_name).await?;
let nrs_url = safe
.nrs_associate(
&format!("a.{site_name}"),
&files_container["/testdata/test.md"],
)
.await?;
safe.nrs_associate(
&format!("b.{site_name}"),
&files_container["/testdata/test.md"],
)
.await?;
safe.nrs_associate(
&format!("a.{site_name}"),
&files_container["/testdata/another.md"],
)
.await?;
safe.nrs_associate(
&format!("a.{site_name}"),
&files_container["/testdata/noextension"],
)
.await?;
let content = safe.fetch(&nrs_url.to_string(), None).await?;
match &content {
SafeData::PublicFile { xorurl, .. } => {
assert_eq!(
*xorurl,
files_container["/testdata/test.md"].to_xorurl_string()
);
}
_ => {
bail!("PublicFile was not returned".to_string());
}
}
let inspected_content = safe.inspect(&nrs_url.to_string()).await?;
match &inspected_content[0] {
SafeData::NrsEntry {
xorurl,
public_name,
data_type,
resolves_into,
resolved_from,
version,
} => {
assert_eq!(
*xorurl,
files_container["/testdata/test.md"].to_xorurl_string()
);
assert_eq!(*public_name, nrs_url.public_name());
assert_eq!(*data_type, files_container["/testdata/test.md"].data_type());
assert_eq!(*resolves_into, files_container["/testdata/test.md"]);
assert_eq!(*resolved_from, nrs_url.to_string());
let left = VersionHash::from(
&version.ok_or_else(|| anyhow!("version should not be None"))?,
);
let right = nrs_url
.content_version()
.ok_or_else(|| anyhow!("version should not be None"))?;
assert_eq!(left, right);
}
_ => {
bail!("NrsEntry was not returned".to_string());
}
}
Ok(())
}
#[tokio::test]
async fn test_fetch_should_resolve_nrs_url_when_input_url_has_path() -> Result<()> {
let site_name = random_nrs_name();
let safe = new_safe_instance().await?;
let files_container = TestDataFilesContainer::get_container(["/testdata/test.md"]).await?;
let mut nrs_url = safe.nrs_create(&site_name).await?;
safe.nrs_associate(&site_name, &files_container.url).await?;
nrs_url.set_path("testdata/test.md");
let content = safe.fetch(&nrs_url.to_string(), None).await?;
match &content {
SafeData::PublicFile { xorurl, .. } => {
assert_eq!(
*xorurl,
files_container["/testdata/test.md"].to_xorurl_string()
);
}
_ => {
bail!("PublicFile was not returned".to_string());
}
}
Ok(())
}
#[tokio::test]
async fn test_fetch_should_resolve_nrs_url_when_input_url_and_target_url_have_paths(
) -> Result<()> {
let site_name = random_nrs_name();
let safe = new_safe_instance().await?;
let files_container = TestDataFilesContainer::get_container(["/testdata/test.md"]).await?;
let mut target_url = files_container.url.clone();
target_url.set_path("testdata");
let mut nrs_url = safe.nrs_create(&site_name).await?;
safe.nrs_associate(&site_name, &target_url).await?;
nrs_url.set_path("test.md");
let content = safe.fetch(&nrs_url.to_string(), None).await?;
match &content {
SafeData::PublicFile { xorurl, .. } => {
assert_eq!(
*xorurl,
files_container["/testdata/test.md"].to_xorurl_string()
);
}
_ => {
bail!("PublicFile was not returned".to_string());
}
}
Ok(())
}
}