use harmos_domain::model::storage::*;
use harmos_domain::{MemorySize, Message};
use harmos_operations::storage::{CaptureFile, FollowFile, FollowProduction, HashFile, ListDirectory, ReadFile, WatchDirectory};
use std::path::PathBuf;
fn roundtrip<T: Message + MemorySize + harmos_domain::schema::Described + PartialEq + std::fmt::Debug>(value: T) {
let bytes = value.encode();
assert_eq!(T::decode(&bytes).unwrap(), value);
let mut protobuf = Vec::new();
harmos_sdk::grpc::encode(&value, &mut protobuf).unwrap();
let prepared = harmos_sdk::grpc::prepare::<T>(harmos_sdk::grpc::DecodeInput::new(&protobuf)).unwrap();
assert!(prepared.memory() >= value.memory_size());
assert_eq!(prepared.decode().unwrap(), value);
}
#[test]
fn directory_contract_roundtrips() {
let selection = DirectorySelection {
root: PathBuf::from("data/measurements"),
recursive: true,
rules: vec![
DirectoryRule {
pattern: "**/*.bin".into(),
include: true,
},
DirectoryRule {
pattern: "private/**".into(),
include: false,
},
],
};
roundtrip(ListDirectory { selection: selection.clone() });
roundtrip(WatchDirectory { selection, include_existing: true });
let entry = DirectoryEntry {
path: "measurements/run.bin".into(),
directory: false,
length: 8192,
};
for event in [
DirectoryEvent::Existing(entry.clone()),
DirectoryEvent::Created(entry.clone()),
DirectoryEvent::Modified(entry),
DirectoryEvent::InitialScanComplete,
DirectoryEvent::Removed { path: "old.bin".into() },
DirectoryEvent::Renamed {
from: "old.bin".into(),
to: "new.bin".into(),
},
DirectoryEvent::RescanRequired,
] {
roundtrip(event);
}
roundtrip(DirectoryCompletion { entries: 1000 });
}
#[test]
fn file_contract_roundtrips() {
let history = StoreHistory {
store: harmos_domain::Identifier::new("files").unwrap(),
epoch: harmos_domain::Identifier::new("first").unwrap(),
};
let metadata = FileMetadata {
id: harmos_domain::Identifier::new("source").unwrap(),
scope: None,
media_type: "application/octet-stream".into(),
expected_length: None,
expected_sha256: None,
};
let file = FileReference {
position: StorageCursor { history: history.clone(), sequence: 1 },
metadata: metadata.clone(),
length: 3,
sha256: [7; 32],
};
let range = ByteRange { offset: 1, limit: Some(2) };
for source in [FileSource::Stored(file), FileSource::Local(PathBuf::from("measurements/échantillon.bin"))] {
roundtrip(source.clone());
roundtrip(ReadFile { source: source.clone(), range });
roundtrip(HashFile { source: source.clone(), range });
for consistency in [CaptureConsistency::Bounded, CaptureConsistency::Stable] {
roundtrip(CaptureFile {
source: source.clone(),
range,
metadata: metadata.clone(),
consistency,
});
}
for start in [FollowStart::Beginning, FollowStart::End, FollowStart::At(2)] {
roundtrip(FollowFile { source: source.clone(), start });
}
}
roundtrip(FollowProduction {
file: StagedFile { history, id: metadata.id },
offset: 2,
});
roundtrip(FileHash { bytes_read: 12345, sha256: [0xa5; 32] });
roundtrip(FileEvent::Data(FileBatch { offset: 1000, bytes: vec![1, 2, 3] }));
roundtrip(FileEvent::Truncated { length: 2 });
roundtrip(FileEvent::Replaced);
let mut short = FileHash { bytes_read: 1, sha256: [0; 32] }.encode();
short[1] = 31;
short.pop();
assert!(FileHash::decode(&short).is_err());
}
#[cfg(unix)]
#[test]
fn path_transport_rejects() {
use std::os::unix::ffi::OsStringExt;
let path = PathBuf::from(std::ffi::OsString::from_vec(vec![b'a', 0xff]));
let source = FileSource::Local(path);
assert!(FileSource::decode(&source.encode()).is_err());
let mut protobuf = Vec::new();
harmos_sdk::grpc::encode(&source, &mut protobuf).unwrap();
let prepared = harmos_sdk::grpc::prepare::<FileSource>(harmos_sdk::grpc::DecodeInput::new(&protobuf)).unwrap();
assert!(prepared.decode().is_err());
}