Merge pull request #1001 from matthiasbeyer/imag-tag/tests

imag-tag/tests
This commit is contained in:
Matthias Beyer 2017-07-19 11:32:43 +02:00 committed by GitHub
commit e4f8d4ec08
8 changed files with 224 additions and 43 deletions

View file

@ -35,3 +35,12 @@ path = "../libimagentrytag"
[dependencies.libimagutil] [dependencies.libimagutil]
path = "../libimagutil" path = "../libimagutil"
[dev-dependencies.libimagutil]
path = "../libimagutil"
default-features = false
features = ["testing"]
[dev-dependencies]
toml-query = "0.3.0"
env_logger = "0.3"

View file

@ -27,8 +27,16 @@ extern crate libimagstore;
extern crate libimagrt; extern crate libimagrt;
extern crate libimagentrytag; extern crate libimagentrytag;
extern crate libimagerror; extern crate libimagerror;
#[macro_use]
extern crate libimagutil; extern crate libimagutil;
#[cfg(test)]
extern crate toml_query;
#[cfg(test)]
extern crate env_logger;
use std::path::PathBuf; use std::path::PathBuf;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
@ -169,3 +177,199 @@ fn list(id: PathBuf, rt: &Runtime) {
} }
} }
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use std::ffi::OsStr;
use toml::value::Value;
use toml_query::read::TomlValueReadExt;
use toml_query::error::Result as TomlQueryResult;
use libimagentrytag::ui::{get_add_tags, get_remove_tags};
use libimagrt::runtime::Runtime;
use libimagstore::storeid::StoreId;
use libimagstore::store::{Result as StoreResult, FileLockEntry};
use super::alter;
make_mock_app! {
app "imag-tag";
modulename mock;
version "0.3.0";
with help "imag-tag mocking app";
}
use self::mock::generate_test_runtime;
use libimagutil::testing::DEFAULT_ENTRY;
fn create_test_default_entry<'a, S: AsRef<OsStr>>(rt: &'a Runtime, name: S) -> StoreResult<StoreId> {
let mut path = PathBuf::new();
path.set_file_name(name);
let id = StoreId::new_baseless(path)?;
let mut entry = rt.store().create(id.clone())?;
entry.get_content_mut().push_str(DEFAULT_ENTRY);
Ok(id)
}
fn get_entry_tags<'a>(entry: &'a FileLockEntry<'a>) -> TomlQueryResult<Option<&'a Value>> {
entry.get_header().read(&"imag.tags".to_owned())
}
fn tags_toml_value<'a, I: IntoIterator<Item = &'static str>>(tags: I) -> Value {
Value::Array(tags.into_iter().map(|s| Value::String(s.to_owned())).collect())
}
fn setup_logging() {
use env_logger;
let _ = env_logger::init().unwrap_or(());
}
#[test]
fn test_tag_add_adds_tag() {
setup_logging();
debug!("Generating runtime");
let rt = generate_test_runtime(vec!["--id", "test", "--add", "foo"]).unwrap();
debug!("Creating default entry");
create_test_default_entry(&rt, "test").unwrap();
let id = PathBuf::from(String::from("test"));
debug!("Getting 'add' tags");
let add = get_add_tags(rt.cli());
debug!("Add-tags: {:?}", add);
debug!("Getting 'remove' tags");
let rem = get_remove_tags(rt.cli());
debug!("Rem-tags: {:?}", rem);
debug!("Altering things");
alter(&rt, id.clone(), add, rem);
debug!("Altered");
let test_entry = rt.store().get(id).unwrap().unwrap();
let test_tags = get_entry_tags(&test_entry).unwrap().unwrap();
assert_ne!(*test_tags, tags_toml_value(vec![]));
assert_eq!(*test_tags, tags_toml_value(vec!["foo"]));
}
#[test]
fn test_tag_add_more_than_remove_adds_tags() {
setup_logging();
debug!("Generating runtime");
let rt = generate_test_runtime(vec!["--id", "test",
"--add", "foo",
"--add", "bar",
"--add", "baz",
"--add", "bub",
"--remove", "foo",
"--remove", "bar",
"--remove", "baz",
]).unwrap();
debug!("Creating default entry");
create_test_default_entry(&rt, "test").unwrap();
let id = PathBuf::from(String::from("test"));
// Manually add tags
let add = get_add_tags(rt.cli());
debug!("Getting 'remove' tags");
let rem = get_remove_tags(rt.cli());
debug!("Rem-tags: {:?}", rem);
debug!("Altering things");
alter(&rt, id.clone(), add, rem);
debug!("Altered");
let test_entry = rt.store().get(id).unwrap().unwrap();
let test_tags = get_entry_tags(&test_entry).unwrap().unwrap();
assert_eq!(*test_tags, tags_toml_value(vec!["bub"]));
}
#[test]
fn test_tag_remove_removes_tag() {
setup_logging();
debug!("Generating runtime");
let rt = generate_test_runtime(vec!["--id", "test", "--remove", "foo"]).unwrap();
debug!("Creating default entry");
create_test_default_entry(&rt, "test").unwrap();
let id = PathBuf::from(String::from("test"));
// Manually add tags
let add = Some(vec![ "foo".to_owned() ]);
debug!("Getting 'remove' tags");
let rem = get_remove_tags(rt.cli());
debug!("Rem-tags: {:?}", rem);
debug!("Altering things");
alter(&rt, id.clone(), add, rem);
debug!("Altered");
let test_entry = rt.store().get(id).unwrap().unwrap();
let test_tags = get_entry_tags(&test_entry).unwrap().unwrap();
assert_eq!(*test_tags, tags_toml_value(vec![]));
}
#[test]
fn test_tag_remove_removes_only_to_remove_tag() {
setup_logging();
debug!("Generating runtime");
let rt = generate_test_runtime(vec!["--id", "test", "--remove", "foo"]).unwrap();
debug!("Creating default entry");
create_test_default_entry(&rt, "test").unwrap();
let id = PathBuf::from(String::from("test"));
// Manually add tags
let add = Some(vec![ "foo".to_owned(), "bar".to_owned() ]);
debug!("Getting 'remove' tags");
let rem = get_remove_tags(rt.cli());
debug!("Rem-tags: {:?}", rem);
debug!("Altering things");
alter(&rt, id.clone(), add, rem);
debug!("Altered");
let test_entry = rt.store().get(id).unwrap().unwrap();
let test_tags = get_entry_tags(&test_entry).unwrap().unwrap();
assert_eq!(*test_tags, tags_toml_value(vec!["bar"]));
}
#[test]
fn test_tag_remove_removes_but_doesnt_crash_on_nonexistent_tag() {
setup_logging();
debug!("Generating runtime");
let rt = generate_test_runtime(vec!["--id", "test", "--remove", "foo", "--remove", "bar"]).unwrap();
debug!("Creating default entry");
create_test_default_entry(&rt, "test").unwrap();
let id = PathBuf::from(String::from("test"));
// Manually add tags
let add = Some(vec![ "foo".to_owned() ]);
debug!("Getting 'remove' tags");
let rem = get_remove_tags(rt.cli());
debug!("Rem-tags: {:?}", rem);
debug!("Altering things");
alter(&rt, id.clone(), add, rem);
debug!("Altered");
let test_entry = rt.store().get(id).unwrap().unwrap();
let test_tags = get_entry_tags(&test_entry).unwrap().unwrap();
assert_eq!(*test_tags, tags_toml_value(vec![]));
}
}

View file

@ -48,6 +48,5 @@ pub mod exec;
pub mod result; pub mod result;
pub mod tag; pub mod tag;
pub mod tagable; pub mod tagable;
pub mod util;
pub mod ui; pub mod ui;

View file

@ -17,6 +17,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// //
use regex::Regex;
pub type Tag = String; pub type Tag = String;
pub type TagSlice<'a> = &'a str; pub type TagSlice<'a> = &'a str;
@ -31,8 +33,9 @@ pub fn is_tag_str(s: &String) -> Result<(), String> {
let is_lower = |s: &String| s.chars().all(|c| c.is_lowercase()); let is_lower = |s: &String| s.chars().all(|c| c.is_lowercase());
let no_whitespace = |s: &String| s.chars().all(|c| !c.is_whitespace()); let no_whitespace = |s: &String| s.chars().all(|c| !c.is_whitespace());
let is_alphanum = |s: &String| s.chars().all(|c| c.is_alphanumeric()); let is_alphanum = |s: &String| s.chars().all(|c| c.is_alphanumeric());
let matches_regex = |s: &String| Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(s).is_some();
if is_lower.and(no_whitespace).and(is_alphanum).filter(s) { if is_lower.and(no_whitespace).and(is_alphanum).and(matches_regex).filter(s) {
Ok(()) Ok(())
} else { } else {
Err(format!("The string '{}' is not a valid tag", s)) Err(format!("The string '{}' is not a valid tag", s))

View file

@ -27,7 +27,7 @@ use error::TagErrorKind;
use error::MapErrInto; use error::MapErrInto;
use result::Result; use result::Result;
use tag::{Tag, TagSlice}; use tag::{Tag, TagSlice};
use util::is_tag; use tag::is_tag_str;
use toml::Value; use toml::Value;
@ -55,7 +55,7 @@ impl Tagable for Value {
return Err(TagErrorKind::TagTypeError.into()); return Err(TagErrorKind::TagTypeError.into());
} }
if tags.iter().any(|t| match *t { if tags.iter().any(|t| match *t {
Value::String(ref s) => !is_tag(s), Value::String(ref s) => !is_tag_str(s).is_ok(),
_ => unreachable!()}) _ => unreachable!()})
{ {
return Err(TagErrorKind::NotATag.into()); return Err(TagErrorKind::NotATag.into());
@ -77,8 +77,8 @@ impl Tagable for Value {
} }
fn set_tags(&mut self, ts: &[Tag]) -> Result<()> { fn set_tags(&mut self, ts: &[Tag]) -> Result<()> {
if ts.iter().any(|tag| !is_tag(tag)) { if ts.iter().any(|tag| !is_tag_str(tag).is_ok()) {
debug!("Not a tag: '{}'", ts.iter().filter(|t| !is_tag(t)).next().unwrap()); debug!("Not a tag: '{}'", ts.iter().filter(|t| !is_tag_str(t).is_ok()).next().unwrap());
return Err(TagErrorKind::NotATag.into()); return Err(TagErrorKind::NotATag.into());
} }
@ -90,7 +90,7 @@ impl Tagable for Value {
} }
fn add_tag(&mut self, t: Tag) -> Result<()> { fn add_tag(&mut self, t: Tag) -> Result<()> {
if !is_tag(&t) { if !try!(is_tag_str(&t).map(|_| true).map_err(|_| TagErrorKind::NotATag.into_error())) {
debug!("Not a tag: '{}'", t); debug!("Not a tag: '{}'", t);
return Err(TagErrorKind::NotATag.into()); return Err(TagErrorKind::NotATag.into());
} }
@ -104,7 +104,7 @@ impl Tagable for Value {
} }
fn remove_tag(&mut self, t: Tag) -> Result<()> { fn remove_tag(&mut self, t: Tag) -> Result<()> {
if !is_tag(&t) { if !try!(is_tag_str(&t).map(|_| true).map_err(|_| TagErrorKind::NotATag.into_error())) {
debug!("Not a tag: '{}'", t); debug!("Not a tag: '{}'", t);
return Err(TagErrorKind::NotATag.into()); return Err(TagErrorKind::NotATag.into());
} }

View file

@ -20,8 +20,7 @@
use clap::{Arg, ArgMatches, App, SubCommand}; use clap::{Arg, ArgMatches, App, SubCommand};
use tag::Tag; use tag::Tag;
use tag::is_tag;
use libimagutil::cli_validators::is_tag;
/// Generates a `clap::SubCommand` to be integrated in the commandline-ui builder for building a /// Generates a `clap::SubCommand` to be integrated in the commandline-ui builder for building a
/// "tags --add foo --remove bar" subcommand to do tagging action. /// "tags --add foo --remove bar" subcommand to do tagging action.

View file

@ -1,24 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use regex::Regex;
pub fn is_tag(s: &str) -> bool {
Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(s).is_some()
}

View file

@ -47,12 +47,3 @@ pub fn is_url(s: String) -> Result<(), String> {
Url::parse(&s).map(|_| ()).map_err(|_| format!("Not a URL: {}", s)) Url::parse(&s).map(|_| ()).map_err(|_| format!("Not a URL: {}", s))
} }
pub fn is_tag(s: String) -> Result<(), String> {
use regex::Regex;
lazy_static! { static ref TAG_RE : Regex = Regex::new("[:alpha:][:word:]*").unwrap(); }
TAG_RE
.is_match(&s)
.as_result((), format!("Not a valid Tag: '{}' - Valid is [a-zA-Z][0-9a-zA-Z]*", s))
}