| 1 | use notebook::discover::{Error, Local, read_external_asset}; |
| 2 | use std::{fs, io}; |
| 3 | |
| 4 | #[test] |
| 5 | fn nested_external_payloads_are_exact_and_empty_is_distinct_from_missing() { |
| 6 | let root = tempfile::tempdir().unwrap(); |
| 7 | let directory = root.path().join("Group 🦀/Section é_onefiles"); |
| 8 | fs::create_dir_all(&directory).unwrap(); |
| 9 | let filename = "2a83ae62-6754-4383-8e2b-4033ff3cfba1.onebin"; |
| 10 | let mut source = Local::open(root.path()).unwrap(); |
| 11 | let section = "Group 🦀/Section é.ONE"; |
| 12 | assert!( |
| 13 | matches!(read_external_asset(&mut source, section, filename, 0), |
| 14 | Err(Error::Io { error, .. }) if error.kind() == io::ErrorKind::NotFound) |
| 15 | ); |
| 16 | fs::write(directory.join(filename), []).unwrap(); |
| 17 | assert!( |
| 18 | read_external_asset(&mut source, section, filename, 0) |
| 19 | .unwrap() |
| 20 | .is_empty() |
| 21 | ); |
| 22 | let bytes: Vec<_> = (0..65537).map(|index| (index % 251) as u8).collect(); |
| 23 | fs::write(directory.join(filename), &bytes).unwrap(); |
| 24 | assert!( |
| 25 | matches!(read_external_asset(&mut source, section, filename, bytes.len()-1), |
| 26 | Err(Error::Io { error, .. }) if error.kind() == io::ErrorKind::FileTooLarge) |
| 27 | ); |
| 28 | assert_eq!( |
| 29 | read_external_asset(&mut source, section, filename, bytes.len()).unwrap(), |
| 30 | bytes |
| 31 | ); |
| 32 | assert_eq!(fs::read(directory.join(filename)).unwrap(), bytes); |
| 33 | } |
| 34 | |
| 35 | #[test] |
| 36 | fn invalid_asset_locations_fail_before_accessing_the_source() { |
| 37 | struct Unused; |
| 38 | impl notebook::discover::Source for Unused { |
| 39 | fn entries(&mut self, _: &str, _: usize) -> io::Result<Vec<notebook::discover::Entry>> { |
| 40 | panic!("Unexpected enumeration") |
| 41 | } |
| 42 | fn read(&mut self, _: &str, _: usize) -> io::Result<Vec<u8>> { |
| 43 | panic!("Unexpected read") |
| 44 | } |
| 45 | } |
| 46 | let filename = "2a83ae62-6754-4383-8e2b-4033ff3cfba1.onebin"; |
| 47 | for section in [ |
| 48 | "", |
| 49 | ".one", |
| 50 | "Group/.one", |
| 51 | "/Section.one", |
| 52 | "../Section.one", |
| 53 | "Group/../Section.one", |
| 54 | "Group\\Section.one", |
| 55 | "Section.onetoc2", |
| 56 | ] { |
| 57 | assert!(matches!( |
| 58 | read_external_asset(&mut Unused, section, filename, 100), |
| 59 | Err(Error::Entry { .. }) |
| 60 | )); |
| 61 | } |
| 62 | for name in [ |
| 63 | "", |
| 64 | "attachment.png", |
| 65 | "../2a83ae62-6754-4383-8e2b-4033ff3cfba1.onebin", |
| 66 | "2a83ae62-6754-4383-8e2b-4033ff3cfba1.onebin:other", |
| 67 | "<invfdo>", |
| 68 | ] { |
| 69 | assert!(matches!( |
| 70 | read_external_asset(&mut Unused, "Section.one", name, 100), |
| 71 | Err(Error::Entry { .. }) |
| 72 | )); |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | #[test] |
| 77 | fn reserved_payload_directories_are_not_notebook_groups() { |
| 78 | let root = tempfile::tempdir().unwrap(); |
| 79 | fs::write( |
| 80 | root.path().join("Section.ONE"), |
| 81 | onestore::create_section("Section.one", "Fixture", "Author").unwrap(), |
| 82 | ) |
| 83 | .unwrap(); |
| 84 | for name in ["section_onefiles", "orphan_onefiles", "ordinary"] { |
| 85 | fs::create_dir(root.path().join(name)).unwrap(); |
| 86 | } |
| 87 | fs::write( |
| 88 | root.path().join("section_onefiles/unfinished.onebin"), |
| 89 | b"payload", |
| 90 | ) |
| 91 | .unwrap(); |
| 92 | let catalog = notebook::discover::discover( |
| 93 | &mut Local::open(root.path()).unwrap(), |
| 94 | notebook::discover::Limits { |
| 95 | entries: 4, |
| 96 | bytes_per_file: 1 << 20, |
| 97 | depth: 1, |
| 98 | }, |
| 99 | ) |
| 100 | .unwrap(); |
| 101 | assert_eq!(catalog.sections.len(), 1); |
| 102 | assert_eq!( |
| 103 | catalog |
| 104 | .groups |
| 105 | .iter() |
| 106 | .map(|group| group.path.as_str()) |
| 107 | .collect::<Vec<_>>(), |
| 108 | ["ordinary"] |
| 109 | ); |
| 110 | } |
| 111 | |
| 112 | #[test] |
| 113 | #[cfg(feature = "smb")] |
| 114 | #[ignore = "requires a disposable Samba mirror of corpus/native-external-assets/notebook at ONESTORE_SMB_NOTEBOOK"] |
| 115 | fn live_external_payloads() { |
| 116 | use onestore::{ |
| 117 | FileDataReference, RevisionIndex, Store, |
| 118 | document::{Document, Kind}, |
| 119 | }; |
| 120 | let root = std::path::Path::new("../../corpus/native-external-assets/notebook"); |
| 121 | let bytes = fs::read(root.join("synthetic.one")).unwrap(); |
| 122 | let store = Store::parse(&bytes).unwrap(); |
| 123 | let index = RevisionIndex::parse(&store).unwrap(); |
| 124 | let document = Document::parse(&index).unwrap(); |
| 125 | let client = notebook::smb::Client::connect( |
| 126 | &std::env::var("ONESTORE_SMB_LAB").unwrap(), |
| 127 | "agent", |
| 128 | notebook::smb::Credentials::default(), |
| 129 | std::time::Duration::from_secs(5), |
| 130 | ) |
| 131 | .unwrap(); |
| 132 | let mut remote = |
| 133 | notebook::discover::Smb::new(&client, &std::env::var("ONESTORE_SMB_NOTEBOOK").unwrap()) |
| 134 | .unwrap(); |
| 135 | let mut local = Local::open(root).unwrap(); |
| 136 | let mut seen = std::collections::BTreeSet::new(); |
| 137 | for node in document |
| 138 | .spaces |
| 139 | .values() |
| 140 | .flat_map(|space| space.revisions.values()) |
| 141 | .flat_map(|revision| revision.nodes.values()) |
| 142 | { |
| 143 | if let Kind::File { |
| 144 | reference: FileDataReference::External(filename), |
| 145 | .. |
| 146 | } = &node.kind |
| 147 | && seen.insert(filename) |
| 148 | { |
| 149 | let expected = fs::read(root.join("synthetic_onefiles").join(filename)).unwrap(); |
| 150 | assert_eq!( |
| 151 | read_external_asset(&mut local, "synthetic.one", filename, expected.len()).unwrap(), |
| 152 | expected |
| 153 | ); |
| 154 | assert_eq!( |
| 155 | read_external_asset(&mut remote, "synthetic.one", filename, expected.len()) |
| 156 | .unwrap(), |
| 157 | expected |
| 158 | ); |
| 159 | } |
| 160 | } |
| 161 | assert_eq!(seen.len(), 3); |
| 162 | } |