Compare commits

..

52 Commits

Author SHA1 Message Date
f704dec080 Removed unneeded wrapper.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-04-10 12:41:43 -04:00
96d4508af2 Removed accidentally created file.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-04-10 12:37:00 -04:00
1bc0242d00 Added the ability to remove routes from router.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-04-03 09:36:00 -04:00
900ce1f7e7 Gave route storage the abiliity to remove routes.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-30 08:59:02 -04:00
807b9ad456 Removed some dead code.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-26 13:12:17 -04:00
837cea4ce0 Added session id to message.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-26 12:18:38 -04:00
678e433632 Finished moving to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-26 08:26:01 -04:00
24edca6415 Moved addition tests to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-26 07:58:24 -04:00
3a608967ef Moved session tests to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-26 07:50:04 -04:00
6a06672697 Moved update tests to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 11:15:31 -04:00
e95f5220b3 Moved delete tests to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 11:07:32 -04:00
524fd4c266 moved trigger tests to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 10:59:27 -04:00
983abfa7c6 Moved query tests to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 10:52:11 -04:00
0c3a75a16d Moved document tests to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 10:43:29 -04:00
28618ff019 Moved support tests to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 10:35:37 -04:00
20e7aa69ff Moved support to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 10:28:25 -04:00
64e9a38f38 Missed a change.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 10:17:52 -04:00
b3ff154110 Moved create from mtt to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 10:13:16 -04:00
83dd91ae17 Created a startup function for session.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 10:03:00 -04:00
83821ee89b Added records function to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 05:37:26 -04:00
d960d8fd03 Added create document to client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 04:52:27 -04:00
316ae06654 Refactor.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-03-25 03:41:32 -04:00
02caf62f29 Completed the creation of the MTT client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-23 14:08:45 -04:00
74c3327802 Began moving database communication into a client.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-22 13:57:12 -04:00
046d71e606 Made regular tables request session info.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-21 11:44:49 -04:00
8a8006c521 Added features to document definition.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-21 08:50:47 -04:00
5ec1330a73 Added language inforation into session.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-20 09:31:56 -04:00
9e81e17a23 Can now add translatiions to universal strings.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-17 10:49:49 -04:00
92c5ac768b Got universal strings to notify for missing translations.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-16 13:30:55 -04:00
2f078bdf32 Completed basic functions of universal string.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-16 08:56:47 -04:00
7d8de156c7 Can add text to a universal string.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-15 14:05:49 -04:00
6014a3bb25 Got a firm start on UniversalStrings.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-14 12:33:40 -04:00
caee3bbe6a Created the ability to store paragraph data.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-13 10:41:34 -04:00
02939f968f Started the field that will eventually become a web page.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-12 14:11:16 -04:00
e5c14d55cd Changed UserAction to ClientAction.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-10 10:54:29 -04:00
1684ab3367 Made message ids its own structure.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-10 10:41:24 -04:00
af0af3373b Made SenderID a structure.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-10 09:57:16 -04:00
fc2149153e Added multilingual test to triggers.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-09 11:43:05 -04:00
ad92e6e6d5 Refactor
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-09 09:21:04 -04:00
d217f511da Altered populate function.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-09 09:01:38 -04:00
b96bbbe21d Corrected trigger tests.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-08 17:05:47 -04:00
c7054e4306 Fixed the ignored session tests.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-08 16:02:08 -04:00
aca474b42c Finished moving tests into lib test.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-06 10:41:10 -05:00
fb91971a1c Moved the rest of the trigger tests to lib.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-05 12:00:35 -05:00
50962e2b68 Moved on_query test to lib testing.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-05 11:13:18 -05:00
bb47a7af31 Moved delete with index test.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-05 08:06:38 -05:00
87e737ff6f Started moved delete tests into library tests.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-04 12:38:45 -05:00
ff9aef6d44 moved final addition testing into lib testing.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-04 11:07:38 -05:00
43aa53fc49 Moved the index update tests to lib testing.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-03 13:00:26 -05:00
d41b5ff418 Moved some update errors into lib testing.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-02 08:47:40 -05:00
f8b5b477ee Started moving update tests into lib.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
2026-03-01 13:33:37 -05:00
00d8283fb9 Started moving update tests into lib. 2026-03-01 13:33:03 -05:00
30 changed files with 2908 additions and 4681 deletions

2426
'

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
mod action_type;
mod addition;
mod calculation;
mod client;
mod delete;
mod message;
mod query;
@@ -8,12 +9,12 @@ mod reply;
mod request_data;
mod show;
mod update;
mod user;
pub use crate::document::{DocDef, Field, FieldType, IndexType, Record, Records};
pub use crate::document::{DocDef, DocFuncType, Field, FieldType, IndexType, Record, Records};
pub use action_type::Action;
pub use addition::Addition;
pub use calculation::{CalcValue, Calculation, Operand};
pub use client::ClientAction;
pub use delete::Delete;
pub use message::MsgAction;
pub use query::Query;
@@ -21,4 +22,3 @@ pub use reply::Reply;
use request_data::RequestData;
pub use show::Show;
pub use update::Update;
pub use user::UserAction;

View File

@@ -7,6 +7,7 @@ pub enum Action {
Delete,
DocumentCreated,
Error,
None,
OnAddition,
OnDelete,
OnQuery,
@@ -27,6 +28,7 @@ impl From<MsgAction> for Action {
MsgAction::Delete(_) => Action::Delete,
MsgAction::DocumentCreated => Action::DocumentCreated,
MsgAction::Error(_) => Action::Error,
MsgAction::None => Action::None,
MsgAction::OnAddition(_) => Action::OnAddition,
MsgAction::OnDelete(_) => Action::OnDelete,
MsgAction::OnQuery(_) => Action::OnQuery,

View File

@@ -2,38 +2,38 @@ use super::{Addition, Delete, DocDef, FieldType, Query, Update};
use crate::{message::MessageAction, name::NameType};
#[derive(Clone, Debug)]
pub enum UserAction {
pub enum ClientAction {
Addition(Addition),
Delete(Delete),
Query(Query),
Update(Update),
}
impl From<Addition> for UserAction {
impl From<Addition> for ClientAction {
fn from(value: Addition) -> Self {
Self::Addition(value)
}
}
impl From<Delete> for UserAction {
impl From<Delete> for ClientAction {
fn from(value: Delete) -> Self {
Self::Delete(value)
}
}
impl From<Query> for UserAction {
impl From<Query> for ClientAction {
fn from(value: Query) -> Self {
Self::Query(value)
}
}
impl From<Update> for UserAction {
impl From<Update> for ClientAction {
fn from(value: Update) -> Self {
Self::Update(value)
}
}
impl MessageAction for UserAction {
impl MessageAction for ClientAction {
fn doc_name(&self) -> &NameType {
match self {
Self::Addition(data) => data.doc_name(),

View File

@@ -1,4 +1,4 @@
use super::{Addition, Delete, DocDef, Query, Records, Reply, Show, Update, UserAction};
use super::{Addition, ClientAction, Delete, DocDef, Query, Records, Reply, Show, Update};
use crate::{
message::MessageAction, mtterror::MTTError, name::NameType, queue::data_director::Register,
};
@@ -10,6 +10,7 @@ pub enum MsgAction {
Delete(Delete),
DocumentCreated,
Error(MTTError),
None,
OnAddition(Records),
OnDelete(Records),
OnQuery(Records),
@@ -30,6 +31,7 @@ impl MessageAction for MsgAction {
Self::Delete(data) => data.doc_name(),
Self::DocumentCreated => &NameType::None,
Self::Error(data) => data.doc_name(),
Self::None => &NameType::None,
Self::OnAddition(data) => data.doc_name(),
Self::OnDelete(data) => data.doc_name(),
Self::OnQuery(data) => data.doc_name(),
@@ -98,13 +100,13 @@ impl From<Update> for MsgAction {
}
}
impl From<UserAction> for MsgAction {
fn from(value: UserAction) -> Self {
impl From<ClientAction> for MsgAction {
fn from(value: ClientAction) -> Self {
match value {
UserAction::Addition(data) => Self::Addition(data),
UserAction::Delete(data) => Self::Delete(data),
UserAction::Query(data) => Self::Query(data),
UserAction::Update(data) => Self::Update(data),
ClientAction::Addition(data) => Self::Addition(data),
ClientAction::Delete(data) => Self::Delete(data),
ClientAction::Query(data) => Self::Query(data),
ClientAction::Update(data) => Self::Update(data),
}
}
}

View File

@@ -1,4 +1,4 @@
use super::{Query, RequestData};
use super::{CalcValue, Query, RequestData};
use crate::{message::MessageAction, name::NameType};
#[derive(Clone, Debug)]
@@ -28,6 +28,14 @@ impl Update {
&mut self.query
}
pub fn add_field<NT, CV>(&mut self, name: NT, field: CV)
where
CV: Into<CalcValue>,
NT: Into<NameType>,
{
self.values.add_field(name, field);
}
pub fn get_values(&self) -> &RequestData {
&self.values
}

View File

@@ -9,7 +9,12 @@ use record::{InternalRecord, InternalRecords, Oid};
pub use clock::Clock;
pub use create::{CreateDoc, IndexType};
pub use definition::DocDef;
pub use field::{Field, FieldType};
pub use definition::{DocDef, DocFuncType};
pub use field::{Field, FieldType, MissingTranslation};
pub use record::{Record, Records};
pub use session::Session;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum DocFeature {
System,
}

View File

@@ -1,6 +1,6 @@
use crate::{
action::{Action, MsgAction, Records},
message::wrapper::Message,
message::Message,
name::{Name, NameType, Names},
queue::{
data_director::{Include, Path, RegMsg, Register},

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
use super::DocFeature;
use crate::{
action::{Action, CalcValue, Field, FieldType, MsgAction},
document::create::IndexType,
@@ -215,6 +216,7 @@ impl PathAction {
#[derive(Clone, Debug)]
pub struct DocDef {
doc_names: Vec<Name>,
features: HashSet<DocFeature>,
field_names: Names,
fields: HashMap<NameID, FieldSetting>,
indexes: HashMap<NameID, IndexType>,
@@ -227,7 +229,22 @@ impl DocDef {
Self::with_names(names)
}
pub fn system(name: Name) -> Self {
let names = vec![name];
Self::system_with_names(names)
}
pub fn system_with_names(names: Vec<Name>) -> Self {
let mut features = HashSet::new();
features.insert(DocFeature::System);
Self::create(names, features)
}
pub fn with_names(names: Vec<Name>) -> Self {
Self::create(names, HashSet::new())
}
fn create(names: Vec<Name>, features: HashSet<DocFeature>) -> Self {
let routes = vec![
PathAction::new(
Path::new(
@@ -272,6 +289,7 @@ impl DocDef {
];
Self {
doc_names: names,
features: features,
field_names: Names::new(),
fields: HashMap::new(),
indexes: HashMap::new(),
@@ -295,12 +313,22 @@ impl DocDef {
&mut self.field_names
}
pub fn add_field(&mut self, name: Name, ftype: FieldType) {
let id = self.field_names.add_names([name].to_vec()).unwrap();
pub fn has_feature(&self, feature: &DocFeature) -> bool {
match self.features.get(feature) {
Some(_) => true,
None => false,
}
}
pub fn add_field(&mut self, names: Vec<Name>, ftype: FieldType) {
let id = self.field_names.add_names(names).unwrap();
self.fields.insert(id, FieldSetting::new(ftype));
}
pub fn get_field_type<NT>(&self, field_name: NT) -> Result<&FieldType, MTTError> where NT: Into<NameType> {
pub fn get_field_type<NT>(&self, field_name: NT) -> Result<&FieldType, MTTError>
where
NT: Into<NameType>,
{
let id = match self.field_names.get_id(field_name) {
Ok(data) => data,
Err(err) => return Err(err),
@@ -403,10 +431,22 @@ mod docdefs {
use crate::action::{Query, Update};
#[test]
fn message_doc_name_returns_none() {
fn can_create_document_definition() {
let docname = Name::english("tester");
let mut docdef = DocDef::new(docname);
assert_eq!(docdef.doc_name(), &NameType::None);
assert!(
docdef.features.is_empty(),
"should not have special features"
);
}
#[test]
fn can_create_system_document_definition() {
let docname = Name::english("system");
let mut docdef = DocDef::system(docname);
assert_eq!(docdef.features.len(), 1);
assert_eq!(docdef.features.iter().last().unwrap(), &DocFeature::System);
}
#[test]
@@ -415,7 +455,7 @@ mod docdefs {
let mut docdef = DocDef::new(docname);
let name = Name::english(Uuid::new_v4().to_string().as_str());
let field_type = FieldType::Uuid;
docdef.add_field(name.clone(), field_type.clone());
docdef.add_field(vec![name.clone()], field_type.clone());
let result = docdef.get_field(name).unwrap();
match result.validate(&Uuid::new_v4().into()) {
Ok(_) => {}
@@ -431,8 +471,8 @@ mod docdefs {
let field2 = Name::english("field2");
let ftype1 = FieldType::Uuid;
let ftype2 = FieldType::StaticString;
docdef.add_field(field1.clone(), ftype1.clone());
docdef.add_field(field2.clone(), ftype2.clone());
docdef.add_field(vec![field1.clone()], ftype1.clone());
docdef.add_field(vec![field2.clone()], ftype2.clone());
assert_eq!(docdef.get_field_type(&field1).unwrap(), &ftype1);
assert_eq!(docdef.get_field_type(&field2).unwrap(), &ftype2);
}
@@ -458,7 +498,7 @@ mod docdefs {
let names = ["one", "two", "three"];
let field_type = FieldType::StaticString;
for name in names.iter() {
docdef.add_field(Name::english(name), field_type.clone());
docdef.add_field(vec![Name::english(name)], field_type.clone());
}
for name in names.iter() {
let result = docdef.get_field(Name::english(name)).unwrap();
@@ -474,7 +514,7 @@ mod docdefs {
let docname = Name::english("something");
let mut docdef = DocDef::new(docname);
let name = Name::english("defaultfunction");
docdef.add_field(name.clone(), FieldType::StaticString);
docdef.add_field(vec![name.clone()], FieldType::StaticString);
docdef.set_default(&name, FieldType::StaticString).unwrap();
match docdef.get_field(name).unwrap().validate(&Field::None) {
Ok(data) => match data {
@@ -504,7 +544,7 @@ mod docdefs {
let docname = Name::english("something");
let mut docdef = DocDef::new(docname);
let name = Name::english("defaultvalue");
docdef.add_field(name.clone(), FieldType::Uuid);
docdef.add_field(vec![name.clone()], FieldType::Uuid);
match docdef.set_default(&name, "fred") {
Ok(data) => unreachable!("got {:?}, should be an error", data),
Err(err) => match err.get_error_ids().back().unwrap() {

View File

@@ -1,7 +1,11 @@
use crate::{ErrorID, MTTError};
use chrono::prelude::*;
use isolang::Language;
use std::{
cmp::Ordering,
collections::HashMap,
ops::{Add, AddAssign},
str::Split,
time::Duration,
};
use uuid::Uuid;
@@ -12,8 +16,8 @@ pub enum Field {
DateTime(DateTime<Utc>),
Duration(Duration),
Integer(i128),
Language(Language),
None,
Revision(Revision),
StaticString(String),
Uuid(Uuid),
}
@@ -70,12 +74,6 @@ impl From<Duration> for Field {
}
}
impl From<Revision> for Field {
fn from(value: Revision) -> Self {
Self::Revision(value)
}
}
impl From<String> for Field {
fn from(value: String) -> Self {
Self::StaticString(value)
@@ -114,6 +112,12 @@ impl From<i32> for Field {
}
}
impl From<Language> for Field {
fn from(value: Language) -> Self {
Self::Language(value)
}
}
impl PartialOrd for Field {
fn partial_cmp(&self, other: &Field) -> Option<Ordering> {
match (self, other) {
@@ -121,7 +125,6 @@ impl PartialOrd for Field {
(Self::DateTime(d1), Self::DateTime(d2)) => d1.partial_cmp(d2),
(Self::Duration(d1), Self::Duration(d2)) => d1.partial_cmp(d2),
(Self::Integer(d1), Self::Integer(d2)) => d1.partial_cmp(d2),
(Self::Revision(d1), Self::Revision(d2)) => d1.partial_cmp(d2),
(Self::StaticString(d1), Self::StaticString(d2)) => d1.partial_cmp(d2),
(Self::Uuid(d1), Self::Uuid(d2)) => d1.partial_cmp(d2),
(_, _) => None,
@@ -276,8 +279,8 @@ pub enum FieldType {
DateTime,
Duration,
Integer,
Language,
None,
Revision,
StaticString,
Uuid,
}
@@ -289,8 +292,8 @@ impl FieldType {
FieldType::DateTime => Utc::now().into(),
FieldType::Duration => Duration::from_secs(0).into(),
FieldType::Integer => 0.into(),
FieldType::Language => Language::from_639_1("en").unwrap().into(),
FieldType::None => Field::None,
FieldType::Revision => Revision::new().into(),
FieldType::StaticString => "".into(),
FieldType::Uuid => Uuid::new_v4().into(),
}
@@ -304,8 +307,8 @@ impl From<&Field> for FieldType {
Field::DateTime(_) => Self::DateTime,
Field::Duration(_) => Self::Duration,
Field::Integer(_) => Self::Integer,
Field::Language(_) => Self::Language,
Field::None => Self::None,
Field::Revision(_) => Self::Revision,
Field::StaticString(_) => Self::StaticString,
Field::Uuid(_) => Self::Uuid,
}
@@ -348,26 +351,636 @@ mod fieldtypes {
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
struct Revision;
#[derive(Clone, Debug)]
enum DataOrigin {
Human,
Computer,
}
impl Revision {
fn new() -> Self {
Self {}
#[derive(Clone, Debug)]
struct ParagraphData {
string: String,
origin: DataOrigin,
initial: bool,
}
impl ParagraphData {
fn initial(string: String) -> Self {
Self {
string: string,
origin: DataOrigin::Human,
initial: true,
}
}
fn revision(&self) -> isize {
0
fn new(string: String, origin: DataOrigin) -> Self {
Self {
string: string,
origin: origin,
initial: false,
}
}
fn get_text(&self) -> &String {
&self.string
}
fn by_human(&self) -> bool {
match self.origin {
DataOrigin::Human => true,
DataOrigin::Computer => false,
}
}
fn is_initial(&self) -> bool {
self.initial
}
}
#[cfg(test)]
mod revisions {
mod paragraph_data {
use super::*;
#[test]
fn can_create_empty_revision() {
let rev = Revision::new();
assert_eq!(rev.revision(), 0);
fn can_determine_initial_information() {
let text = Uuid::new_v4().to_string();
let data = ParagraphData::initial(text.clone());
assert_eq!(data.get_text(), &text);
assert!(data.by_human(), "{:?} should have returned true", data);
assert!(data.is_initial(), "{:?} should have returned true", data);
}
#[test]
fn can_be_made_by_humans() {
let text = Uuid::new_v4().to_string();
let data = ParagraphData::new(text.clone(), DataOrigin::Human);
assert_eq!(data.get_text(), &text);
assert!(data.by_human(), "{:?} should have returned true", data);
assert!(!data.is_initial(), "{:?} should have returned false", data);
}
#[test]
fn can_be_made_by_computers() {
let text = Uuid::new_v4().to_string();
let data = ParagraphData::new(text.clone(), DataOrigin::Computer);
assert_eq!(data.get_text(), &text);
assert!(!data.by_human(), "{:?} should have returned false", data);
assert!(!data.is_initial(), "{:?} should have returned false", data);
}
}
#[derive(Clone, Debug)]
struct Paragraph {
data: HashMap<Language, ParagraphData>,
}
impl Paragraph {
fn new(lang: Language, string: String) -> Self {
let mut data = HashMap::new();
data.insert(lang, ParagraphData::initial(string));
Self { data: data }
}
fn add_translation(&mut self, lang: Language, text: String) {
match self.data.get(&lang) {
Some(_) => {}
None => {
self.data
.insert(lang, ParagraphData::new(text, DataOrigin::Computer));
}
};
}
fn improve_translation(&mut self, lang: Language, text: String) {
self.data
.insert(lang, ParagraphData::new(text, DataOrigin::Human));
}
fn get(&self, lang: &Language) -> Option<&String> {
match self.data.get(lang) {
Some(data) => Some(data.get_text()),
None => None,
}
}
fn get_initial(&self) -> (&Language, &String) {
for (lang, data) in self.data.iter() {
if data.is_initial() {
return (lang, data.get_text());
}
}
unreachable!("paragraph should initialize with data");
}
fn by_humans(&self) -> HashMap<Language, String> {
let mut output = HashMap::new();
for (lang, data) in self.data.iter() {
if data.by_human() {
output.insert(lang.clone(), data.get_text().clone());
}
}
output
}
}
#[cfg(test)]
mod paragraphs {
use super::*;
#[test]
fn does_paragraph_store_language_information() {
let languages = [
Language::from_639_1("en").unwrap(),
Language::from_639_1("ja").unwrap(),
];
let data = Uuid::new_v4().to_string();
for lang in languages.iter() {
let result = Paragraph::new(lang.clone(), data.clone());
assert_eq!(result.get(lang).unwrap(), &data);
}
}
#[test]
fn are_multiple_languages_stored() {
let text = ["test", "テスト"];
let languages = [
Language::from_639_1("en").unwrap(),
Language::from_639_1("ja").unwrap(),
];
let mut paragraph = Paragraph::new(languages[0].clone(), text[0].clone().to_string());
paragraph.add_translation(languages[1].clone(), text[1].clone().to_string());
for i in 0..text.len() {
assert_eq!(paragraph.get(&languages[i]).unwrap(), text[i]);
}
}
#[test]
fn does_add_translation_get_ignored_if_it_already_exists() {
let text = "something";
let lang = Language::from_639_1("en").unwrap();
let mut paragraph = Paragraph::new(lang.clone(), text.to_string());
paragraph.add_translation(lang, "other".to_string());
assert_eq!(paragraph.get(&lang).unwrap(), text);
}
#[test]
fn does_improve_translation_replace_existing() {
let text = "new";
let lang = Language::from_639_1("en").unwrap();
let mut paragraph = Paragraph::new(lang.clone(), "old".to_string());
paragraph.improve_translation(lang.clone(), text.to_string());
assert_eq!(paragraph.get(&lang).unwrap(), text);
}
#[test]
fn can_determine_human_text() {
let text = "something";
let lang = Language::from_639_1("en").unwrap();
let paragraph = Paragraph::new(lang.clone(), text.to_string());
let result = paragraph.by_humans();
assert_eq!(result.len(), 1, "got wrong numnber of texts");
assert_eq!(result.get(&lang).unwrap(), text);
}
#[test]
fn add_translation_does_not_count_as_human_text() {
let text = "test";
let lang = Language::from_639_1("en").unwrap();
let mut paragraph = Paragraph::new(lang.clone(), text.to_string());
paragraph.add_translation(Language::from_639_1("ja").unwrap(), "テスト".to_string());
let result = paragraph.by_humans();
assert_eq!(result.len(), 1, "got wrong numnber of texts");
assert_eq!(result.get(&lang).unwrap(), text);
}
#[test]
fn impove_translation_does_get_added_as_human() {
let text = ["test", "テスト"];
let languages = [
Language::from_639_1("en").unwrap(),
Language::from_639_1("ja").unwrap(),
];
let mut paragraph = Paragraph::new(languages[0].clone(), text[0].clone().to_string());
paragraph.improve_translation(languages[1].clone(), text[1].clone().to_string());
let result = paragraph.by_humans();
assert_eq!(result.len(), 2, "got wrong numnber of texts");
for i in 0..text.len() {
assert_eq!(result.get(&languages[i]).unwrap(), text[i]);
}
}
#[test]
fn can_get_original_text() {
let text = Uuid::nil().to_string();
let lang = Language::from_639_1("en").unwrap();
let mut paragraph = Paragraph::new(lang.clone(), text.clone());
paragraph.add_translation(
Language::from_639_1("ja").unwrap(),
Uuid::new_v4().to_string(),
);
paragraph.improve_translation(
Language::from_639_1("de").unwrap(),
Uuid::new_v4().to_string(),
);
let (rlang, rtext) = paragraph.get_initial();
assert_eq!(rlang, &lang);
assert_eq!(rtext, &text);
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct ParagraphID {
data: Uuid,
}
impl ParagraphID {
fn new() -> Self {
Self {
data: Uuid::new_v4(),
}
}
fn nil() -> Self {
Self { data: Uuid::nil() }
}
}
#[cfg(test)]
mod paragraphids {
use super::*;
#[test]
fn are_paragraph_ids_unique() {
let id1 = ParagraphID::new();
let id2 = ParagraphID::new();
assert_ne!(id1, id2);
}
#[test]
fn can_create_nil() {
let id = ParagraphID::nil();
assert_eq!(id.data, Uuid::nil());
}
}
#[derive(Clone, Debug)]
pub struct MissingTranslation {
id: ParagraphID,
needs: Language,
has: Language,
text: String,
}
impl MissingTranslation {
fn new(id: ParagraphID, needs: Language, has: Language, text: String) -> Self {
Self {
id: id,
needs: needs,
has: has,
text: text,
}
}
fn paragraph_id(&self) -> ParagraphID {
self.id.clone()
}
fn needs(&self) -> Language {
self.needs.clone()
}
fn has(&self) -> Language {
self.has.clone()
}
fn text(&self) -> String {
self.text.clone()
}
}
#[cfg(test)]
mod missing_translations {
use super::*;
#[test]
fn can_get_mising_translation_information() {
let id = ParagraphID::new();
let langs = [
Language::from_639_1("en").unwrap(),
Language::from_639_1("ja").unwrap(),
];
let text = Uuid::new_v4().to_string();
let missing =
MissingTranslation::new(id.clone(), langs[0].clone(), langs[1].clone(), text.clone());
assert_eq!(missing.paragraph_id(), id);
assert_eq!(missing.needs(), langs[0]);
assert_eq!(missing.has(), langs[1]);
assert_eq!(missing.text(), text);
let missing2 =
MissingTranslation::new(id.clone(), langs[1].clone(), langs[0].clone(), text.clone());
assert_eq!(missing.paragraph_id(), id);
assert_eq!(missing2.needs(), langs[1]);
assert_eq!(missing2.has(), langs[0]);
assert_eq!(missing2.text(), text);
}
}
#[derive(Clone, Debug)]
struct UniversalString {
paragraphs: HashMap<ParagraphID, Paragraph>,
revisions: Vec<Vec<ParagraphID>>,
}
impl UniversalString {
fn new(lang: Language, text: String) -> Self {
let mut output = Self {
paragraphs: HashMap::new(),
revisions: Vec::new(),
};
output.update(lang, text);
output
}
fn get(&self, lang: &Language) -> Result<String, MTTError> {
let latest = self.revisions.len() - 1;
self.get_revision(latest, lang)
}
fn get_revision(&self, rev_num: usize, lang: &Language) -> Result<String, MTTError> {
let mut output = "".to_string();
let mut missing: Vec<MissingTranslation> = Vec::new();
for id in self.revisions[rev_num].iter() {
let paragraph = self.paragraphs.get(id).unwrap();
let text = match paragraph.get(lang) {
Some(data) => data,
None => {
let (ori_lang, text) = paragraph.get_initial();
missing.push(MissingTranslation::new(
id.clone(),
lang.clone(),
ori_lang.clone(),
text.clone(),
));
""
}
};
output += text;
output += "\u{2029}";
}
if missing.is_empty() {
Ok(output)
} else {
Err(MTTError::new(ErrorID::MissingTranslation(missing)))
}
}
fn revision_count(&self) -> usize {
self.revisions.len() - 1
}
fn update(&mut self, lang: Language, text: String) {
let mut version = Vec::new();
for paragraph in text.as_str().split("\u{2029}") {
if paragraph != "" {
let mut id = ParagraphID::nil();
for (key, value) in self.paragraphs.iter() {
if &paragraph == value.get(&lang).unwrap() {
id = key.clone();
break;
}
}
if id == ParagraphID::nil() {
id = ParagraphID::new();
while self.paragraphs.contains_key(&id) {
id = ParagraphID::new();
}
self.paragraphs.insert(
id.clone(),
Paragraph::new(lang.clone(), paragraph.to_string()),
);
}
version.push(id);
}
}
self.revisions.push(version);
}
fn add_translation(&mut self, id: ParagraphID, lang: Language, text: String) {
self.paragraphs
.get_mut(&id)
.unwrap()
.add_translation(lang, text);
}
fn improve_translation(&mut self, id: ParagraphID, lang: Language, text: String) {
self.paragraphs
.get_mut(&id)
.unwrap()
.improve_translation(lang, text);
}
fn by_humans(&self) -> Vec<(ParagraphID, HashMap<Language, String>)> {
let mut output = Vec::new();
let latest = self.revisions.iter().last().unwrap();
for id in latest.iter() {
let para = self.paragraphs.get(id).unwrap();
output.push((id.clone(), para.by_humans()));
}
output
}
}
#[cfg(test)]
mod universal_strings {
use super::*;
use rand::random_range;
use std::collections::HashSet;
const ENGLISH_DATA: [&str; 5] = ["one", "two", "three", "four", "five"];
const JAPANESE_DATA: [&str; 5] = ["", "", "", "", ""];
struct TestData;
impl TestData {
fn english() -> (Language, Vec<String>) {
let lang = Language::from_639_1("en").unwrap();
let mut data = Vec::new();
for text in ENGLISH_DATA.iter() {
data.push(text.to_string());
}
(lang, data)
}
fn japanese() -> (Language, Vec<String>) {
let lang = Language::from_639_1("ja").unwrap();
let mut data = Vec::new();
for text in JAPANESE_DATA.iter() {
data.push(text.to_string());
}
(lang, data)
}
fn to_input(data: Vec<String>) -> String {
let mut output = "".to_string();
for paragraph in data.iter() {
output += paragraph;
output += "\u{2029}";
}
output
}
}
#[test]
fn are_initial_strings_empty() {
let text = ["test", "テスト"];
let languages = [
Language::from_639_1("en").unwrap(),
Language::from_639_1("ja").unwrap(),
];
for i in 0..text.len() {
let ustr = UniversalString::new(languages[i].clone(), text[i].to_string());
let expected = text[i].to_string() + "\u{2029}";
assert_eq!(ustr.get(&languages[i]).unwrap(), expected);
assert_eq!(ustr.revision_count(), 0);
assert_eq!(ustr.paragraphs.len(), 1);
}
}
#[test]
fn accepts_strings_with_multiple_paragraphs() {
let (lang, data) = TestData::english();
let input = TestData::to_input(data.clone());
let ustr = UniversalString::new(lang.clone(), input.clone());
assert_eq!(ustr.get(&lang).unwrap(), input);
assert_eq!(ustr.revision_count(), 0);
assert_eq!(ustr.paragraphs.len(), data.len(), "{:?}", ustr);
}
#[test]
fn can_insert_text_into_string() {
let (lang, mut data) = TestData::english();
let initial = TestData::to_input(data.clone());
let mut ustr = UniversalString::new(lang.clone(), initial.clone());
let position = random_range(..data.len());
data.insert(position, "something".to_string());
let expected = TestData::to_input(data.clone());
ustr.update(lang.clone(), expected.clone());
assert_eq!(ustr.get(&lang).unwrap(), expected);
assert_eq!(ustr.revision_count(), 1);
assert_eq!(ustr.paragraphs.len(), data.len(), "{:?}", ustr);
assert_eq!(ustr.get_revision(0, &lang).unwrap(), initial);
}
#[test]
fn can_a_paragraph_be_replaced() {
let (lang, mut data) = TestData::english();
let initial = TestData::to_input(data.clone());
let mut ustr = UniversalString::new(lang.clone(), initial.clone());
let position = random_range(..data.len());
data[position] = "replaced".to_string();
let expected = TestData::to_input(data.clone());
ustr.update(lang.clone(), expected.clone());
assert_eq!(ustr.get(&lang).unwrap(), expected);
assert_eq!(ustr.revision_count(), 1);
assert_eq!(ustr.paragraphs.len(), (data.len() + 1), "{:?}", ustr);
assert_eq!(ustr.get_revision(0, &lang).unwrap(), initial);
}
#[test]
fn does_not_store_duplicate_data() {
let lang = Language::from_639_1("en").unwrap();
let mut data = Vec::new();
for _ in 0..3 {
data.push("same".to_string());
}
let initial = TestData::to_input(data.clone());
let mut ustr = UniversalString::new(lang.clone(), initial.clone());
assert_eq!(ustr.get(&lang).unwrap(), initial);
assert_eq!(ustr.paragraphs.len(), 1, "{:?}", ustr);
}
#[test]
fn can_text_be_removed() {
let (lang, mut data) = TestData::english();
let expected_paragraphs = data.len();
let initial = TestData::to_input(data.clone());
let mut ustr = UniversalString::new(lang.clone(), initial.clone());
let position = random_range(..data.len());
data.remove(position);
let expected = TestData::to_input(data.clone());
ustr.update(lang.clone(), expected.clone());
assert_eq!(ustr.get(&lang).unwrap(), expected);
assert_eq!(ustr.revision_count(), 1);
assert_eq!(ustr.paragraphs.len(), expected_paragraphs, "{:?}", ustr);
assert_eq!(ustr.get_revision(0, &lang).unwrap(), initial);
}
#[test]
fn can_translation_be_added() {
let (elang, edata) = TestData::english();
let (jlang, jdata) = TestData::japanese();
let initial = TestData::to_input(jdata.clone());
let expected = TestData::to_input(edata.clone());
let mut ustr = UniversalString::new(jlang.clone(), initial.clone());
assert_eq!(ustr.get(&jlang).unwrap(), initial);
let err = ustr.get(&elang).unwrap_err();
match err.get_error_ids().iter().last().unwrap() {
ErrorID::MissingTranslation(missing) => {
assert_eq!(
missing.len(),
jdata.len(),
"should return list of translations needed"
);
let mut holder: HashSet<String> = jdata.iter().cloned().collect();
for data in missing.iter() {
assert_eq!(data.needs(), elang, "needed language is incorrect");
assert_eq!(data.has(), jlang, "original language is incorrect");
assert!(holder.contains(&data.text()));
let text = data.text();
holder.remove(&text);
let index = jdata.iter().position(|x| x == &text).unwrap();
ustr.add_translation(data.paragraph_id(), elang.clone(), edata[index].clone());
}
assert!(holder.is_empty(), "still had {:?}", holder);
}
_ => unreachable!("got {:?}, should have been needs translation", err),
}
assert_eq!(ustr.revision_count(), 0);
assert_eq!(ustr.get(&elang).unwrap(), expected);
let result = ustr.by_humans();
assert_eq!(
result.len(),
jdata.len(),
"incorrect number of original values"
);
let mut count = 0;
for (_, lang_result) in result {
assert_eq!(lang_result.len(), 1, "wrong number of returned languages");
assert_eq!(lang_result.get(&jlang).unwrap(), &jdata[count]);
count += 1;
}
}
#[test]
fn can_translations_be_improved() {
let (elang, edata) = TestData::english();
let (jlang, jdata) = TestData::japanese();
let initial = TestData::to_input(edata.clone());
let expected = TestData::to_input(jdata.clone());
let mut ustr = UniversalString::new(elang.clone(), initial.clone());
for (id, lang_text) in ustr.by_humans().iter() {
let text = lang_text.get(&elang).unwrap();
let index = edata.iter().position(|x| x == text).unwrap();
ustr.improve_translation(id.clone(), jlang, jdata[index].clone());
}
assert_eq!(ustr.revision_count(), 0);
assert_eq!(ustr.get(&jlang).unwrap(), expected);
let mut count = 0;
for (_, lang_result) in ustr.by_humans().iter() {
assert_eq!(lang_result.len(), 2, "wrong number of returned languages");
assert_eq!(lang_result.get(&elang).unwrap(), &edata[count]);
assert_eq!(lang_result.get(&jlang).unwrap(), &jdata[count]);
count += 1;
}
}
}

View File

@@ -5,7 +5,7 @@ use crate::{
create::IndexType,
definition::{DocDef, DocFuncType},
},
message::wrapper::Message,
message::{Message, MessageID},
name::{Name, NameType},
queue::{
data_director::{Include, Path, RegMsg, Register},
@@ -20,30 +20,75 @@ impl Session {
pub fn doc_names() -> Vec<Name> {
let mut names = Vec::new();
names.push(Name::english("session"));
names.push(Name::japanese("セッション"));
names
}
pub fn id_field_names() -> Vec<Name> {
let mut names = Vec::new();
names.push(Name::english("id"));
names.push(Name::japanese("身元"));
names
}
pub fn expire_field_names() -> Vec<Name> {
let mut names = Vec::new();
names.push(Name::english("expire"));
names.push(Name::japanese("期限切れ"));
names
}
pub fn language_field_names() -> Vec<Name> {
let mut names = Vec::new();
names.push(Name::english("language"));
names.push(Name::japanese("言語"));
names
}
pub fn start(mut queue: Queue) {
let (tx, rx) = channel();
let msg_id = MessageID::new();
let sender_id = queue.add_sender(tx);
let path = Path::new(
Include::Just(msg_id.clone()),
Include::All,
Include::Just(Action::DocumentCreated),
);
let reg_msg = Register::new(sender_id.clone(), RegMsg::AddRoute(path.clone()));
let msg = Message::new(reg_msg).set_id(msg_id);
queue.send(msg.clone());
rx.recv().unwrap(); // Wait for completion.
queue.send(msg.set_action(Self::document_definition()));
rx.recv().unwrap(); // Wait for completion.
queue.remove_sender(&sender_id);
}
pub fn document_definition() -> DocDef {
let mut docdef = DocDef::with_names(Self::doc_names());
let name_id = Self::id_field_names()[0].clone();
let name_expire = Self::expire_field_names()[0].clone();
let name_lang = Self::language_field_names()[0].clone();
let mut docdef = DocDef::system_with_names(Self::doc_names());
let mut calc = Calculation::new(Operand::Add);
calc.add_value(FieldType::DateTime).unwrap();
calc.add_value(Duration::from_hours(1)).unwrap();
let name_id = Name::english("id");
docdef.add_field(name_id.clone(), FieldType::Uuid);
docdef.add_field(Self::id_field_names(), FieldType::Uuid);
docdef.set_default(&name_id, FieldType::Uuid).unwrap();
docdef.add_index(&name_id, IndexType::Unique).unwrap();
let name_expire = Name::english("expire");
docdef.add_field(name_expire.clone(), FieldType::DateTime);
docdef.add_field(Self::expire_field_names(), FieldType::DateTime);
docdef.set_default(&name_expire, calc.clone()).unwrap();
docdef.add_index(&name_expire, IndexType::Index).unwrap();
docdef.add_field(Self::language_field_names(), FieldType::Language);
docdef.set_default(&name_lang, FieldType::Language).unwrap();
let mut update = Update::new(Session::doc_names()[0].clone());
update
.get_values_mut()
.add_field(name_expire.clone(), calc.clone());
.add_field(Self::expire_field_names()[0].clone(), calc.clone());
let path = Path::new(
Include::All,
Include::Just(Session::doc_names()[0].clone().into()),
@@ -55,11 +100,11 @@ impl Session {
let mut delete = Delete::new(Session::doc_names()[0].clone());
let delete_qry = delete.get_query_mut();
let mut delete_calc = Calculation::new(Operand::LessThan);
delete_calc.add_value(FieldType::DateTime).unwrap();
delete_calc
.add_value(CalcValue::Existing(FieldType::DateTime))
.unwrap();
delete_qry.add(name_expire.clone(), delete_calc);
delete_calc.add_value(FieldType::DateTime).unwrap();
delete_qry.add(Self::expire_field_names()[0].clone(), delete_calc);
let delete_func = DocFuncType::Trigger(delete.into());
docdef.add_route(Clock::get_path(), delete_func);

View File

@@ -5,19 +5,22 @@ mod mtterror;
pub mod name;
mod queue;
pub use action::*;
use document::{Clock, CreateDoc, Session};
use message::{wrapper::Message, MessageAction};
use isolang::Language;
use message::{Message, MessageAction, MessageID};
use queue::{
data_director::{RegMsg, Register},
router::Queue,
SenderID,
};
use std::{
sync::mpsc::{channel, Receiver, Sender},
sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender},
time::Duration,
};
use uuid::Uuid;
pub use action::*;
pub use document::MissingTranslation;
pub use mtterror::{ErrorID, MTTError};
pub use name::{Name, NameType};
pub use queue::data_director::{Include, Path};
@@ -36,82 +39,23 @@ mod support_tests {
static TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Clone)]
pub struct MoreThanText {
pub struct MTTClient {
queue: Queue,
rx: Receiver<Message>,
sender_id: SenderID,
session_id: Uuid,
}
impl MoreThanText {
pub fn new() -> Self {
let queue = Queue::new();
let mut output = Self {
queue: queue.clone(),
};
Clock::start(queue.clone());
CreateDoc::start(queue.clone());
output
.create_document(Session::document_definition())
.unwrap();
output
}
fn new_session() -> UserAction {
Addition::new(Session::doc_names()[0].clone()).into()
}
fn recursive_message_request<UA>(&mut self, action: UA) -> Uuid
where
UA: Into<UserAction>,
{
match self.records(action) {
Ok(data) => {
if data.len() == 0 {
self.recursive_message_request(MoreThanText::new_session())
} else {
let rec = data.iter().last().unwrap();
match rec.get(Name::english("id")).unwrap() {
Field::Uuid(id) => id,
_ => unreachable!("should always return uuid"),
}
}
}
Err(_) => self.recursive_message_request(MoreThanText::new_session()),
}
}
pub fn validate_session(&mut self, session: Option<String>) -> Uuid {
let action = match session {
Some(data) => match Uuid::try_from(data.as_str()) {
Ok(id) => {
let mut query = Query::new(Session::doc_names()[0].clone());
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(CalcValue::Existing(FieldType::Uuid))
.unwrap();
calc.add_value(id).unwrap();
query.add(Name::english("id"), calc);
query.into()
}
Err(_) => MoreThanText::new_session(),
},
None => MoreThanText::new_session(),
};
self.recursive_message_request(action)
}
pub fn records<UA>(&mut self, request: UA) -> Result<Records, MTTError>
where
UA: Into<UserAction>,
{
let req = request.into();
impl MTTClient {
fn new(mut queue: Queue, sess_id: Option<String>, lang: Option<Language>) -> Self {
let sess_name = Session::doc_names()[0].clone();
let (tx, rx) = channel();
let sender_id = self.queue.add_sender(tx);
let doc_id = req.doc_name().clone();
let msg = Message::new(req);
let msg_id = msg.get_message_id();
let sender_id = queue.add_sender(tx);
let msg_id = MessageID::new();
let paths = [
Path::new(
Include::Just(msg_id.clone()),
Include::Just(doc_id.clone()),
Include::Just(sess_name.clone().into()),
Include::Just(Action::Records),
),
Path::new(
@@ -120,42 +64,71 @@ impl MoreThanText {
Include::Just(Action::Error),
),
];
for path in paths.iter() {
let reg_msg = Register::new(sender_id.clone(), RegMsg::AddRoute(path.clone()));
self.queue.send(Message::new(reg_msg));
let result = rx.recv().unwrap();
let action = result.get_action();
match action {
MsgAction::Register(status) => match status.get_msg() {
RegMsg::Error(err) => {
let mut error = err.clone();
error.add_parent(ErrorID::Document(msg.doc_name().clone()));
self.queue.remove_sender(&sender_id);
return Err(error);
}
_ => {}
},
_ => unreachable!("got {:?} should have been a registry message", action),
let mut add = Addition::new(Session::doc_names()[0].clone());
match lang {
Some(language) => {
let field: Field = language.into();
add.add_field(Session::language_field_names()[0].clone(), field);
}
None => {}
}
self.queue.send(msg);
let output = match rx.recv_timeout(TIMEOUT) {
Ok(data) => match data.get_action() {
MsgAction::Records(data) => Ok(data.clone()),
MsgAction::Error(err) => Err(err.clone()),
_ => unreachable!("should only receive records or errors"),
},
Err(_) => Err(MTTError::new(ErrorID::TimeOut)),
let msg = Message::default().set_id(msg_id.clone());
for path in paths.iter().cloned() {
let reg_msg = Register::new(sender_id.clone(), RegMsg::AddRoute(path));
queue.send(msg.set_action(reg_msg));
let result = rx.recv().unwrap();
}
match sess_id {
Some(id) => {
let sess_id = match Uuid::try_from(id.as_str()) {
Ok(data) => {
let mut qry = Query::new(Session::doc_names()[0].clone());
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(CalcValue::Existing(FieldType::Uuid))
.unwrap();
calc.add_value(data.clone()).unwrap();
qry.add(Session::id_field_names()[0].clone(), calc);
queue.send(msg.set_action(qry));
}
Err(_) => queue.send(msg.set_action(add.clone())),
};
}
None => queue.send(msg.set_action(add.clone())),
};
self.queue.remove_sender(&sender_id);
output
let result = rx.recv().unwrap();
let session_id = match result.get_action() {
MsgAction::Records(result) => {
let mut holder = result.clone();
if holder.len() == 0 {
queue.send(msg.set_action(add));
let new_sess = rx.recv().unwrap();
holder = match new_sess.get_action() {
MsgAction::Records(new_holder) => new_holder.clone(),
_ => unreachable!("should only receive session records"),
}
}
let rec = holder.iter().last().unwrap();
match rec.get(Session::id_field_names()[0].clone()).unwrap() {
Field::Uuid(data) => data.clone(),
_ => unreachable!("should always be uuid"),
}
}
_ => unreachable!("should only receive session records"),
};
Self {
queue: queue,
rx: rx,
sender_id: sender_id,
session_id: session_id,
}
}
pub fn create_document(&mut self, docdef: DocDef) -> Result<(), MTTError> {
let (tx, rx) = channel();
let sender_id = self.queue.add_sender(tx);
let msg = Message::new(docdef);
let msg_id = msg.get_message_id();
pub fn session_id(&self) -> String {
self.session_id.to_string()
}
pub fn create_document(&self, docdef: DocDef) -> Result<(), MTTError> {
let msg_id = MessageID::new();
let paths = [
Path::new(
Include::Just(msg_id.clone()),
@@ -168,22 +141,106 @@ impl MoreThanText {
Include::Just(Action::Error),
),
];
let msg = Message::default()
.set_id(msg_id)
.set_session(self.session_id.clone().into());
for path in paths.iter() {
let reg_msg = Register::new(sender_id.clone(), RegMsg::AddRoute(path.clone()));
self.queue.send(Message::new(reg_msg));
rx.recv().unwrap(); // Wait for completion.
let reg_msg = Register::new(self.sender_id.clone(), RegMsg::AddRoute(path.clone()));
self.queue.send(msg.set_action(reg_msg));
self.rx.recv().unwrap(); // Wait for completion.
}
self.queue.send(msg);
let output = match rx.recv_timeout(TIMEOUT) {
self.queue.send(msg.set_action(docdef));
match self.rx.recv_timeout(TIMEOUT) {
Ok(data) => match data.get_action() {
MsgAction::DocumentCreated => Ok(()),
MsgAction::Error(err) => Err(err.clone()),
_ => unreachable!("should only receive confirmation or errors"),
},
Err(_) => Err(MTTError::new(ErrorID::TimeOut)),
}
}
pub fn records<UA>(&self, request: UA) -> Result<Records, MTTError>
where
UA: Into<ClientAction>,
{
let req = request.into();
let doc_id = req.doc_name().clone();
let msg_id = MessageID::new();
let paths = [
Path::new(
Include::Just(msg_id.clone()),
Include::Just(doc_id.clone()),
Include::Just(Action::Records),
),
Path::new(
Include::Just(msg_id.clone()),
Include::All,
Include::Just(Action::Error),
),
];
let msg = Message::default()
.set_id(msg_id.clone())
.set_session(self.session_id.clone().into());
for path in paths.iter() {
let reg_msg = Register::new(self.sender_id.clone(), RegMsg::AddRoute(path.clone()));
self.queue.send(msg.set_action(reg_msg));
let result = self.rx.recv().unwrap();
let action = result.get_action();
match action {
MsgAction::Register(status) => match status.get_msg() {
RegMsg::Error(err) => {
let mut error = err.clone();
error.add_parent(ErrorID::Document(doc_id.clone()));
return Err(error);
}
_ => {}
},
_ => unreachable!("got {:?} should have been a registry message", action),
}
}
self.queue.send(msg.set_action(req));
match self.rx.recv_timeout(TIMEOUT) {
Ok(data) => match data.get_action() {
MsgAction::Records(data) => Ok(data.clone()),
MsgAction::Error(err) => Err(err.clone()),
_ => unreachable!("should only receive records or errors"),
},
Err(_) => Err(MTTError::new(ErrorID::TimeOut)),
};
self.queue.remove_sender(&sender_id);
output
}
}
}
impl Drop for MTTClient {
fn drop(&mut self) {
self.queue.remove_sender(&self.sender_id);
}
}
#[derive(Clone)]
pub struct MoreThanText {
queue: Queue,
}
impl MoreThanText {
pub fn new() -> Self {
let queue = Queue::new();
CreateDoc::start(queue.clone()); // needs to be first.
Clock::start(queue.clone());
Session::start(queue.clone());
Self { queue: queue }
}
pub fn client(&self) -> MTTClient {
MTTClient::new(self.queue.clone(), None, None)
}
pub fn client_with_language(&self, lang: Language) -> MTTClient {
MTTClient::new(self.queue.clone(), None, Some(lang))
}
pub fn client_with_session(&self, id: String, lang: Option<Language>) -> MTTClient {
MTTClient::new(self.queue.clone(), Some(id), lang)
}
pub fn get_document(&self, name: &str, id: &str) -> Result<String, MTTError> {
@@ -198,6 +255,7 @@ impl MoreThanText {
pub struct TestMoreThanText {
mtt: MoreThanText,
queue: Queue,
channel: Option<Receiver<Message>>,
}
impl TestMoreThanText {
@@ -207,22 +265,12 @@ impl TestMoreThanText {
Self {
mtt: mtt,
queue: queue,
channel: None,
}
}
pub fn validate_session(&mut self, session: Option<String>) -> Uuid {
self.mtt.validate_session(session)
}
pub fn records<UA>(&mut self, request: UA) -> Result<Records, MTTError>
where
UA: Into<UserAction>,
{
self.mtt.records(request)
}
pub fn create_document(&mut self, docdef: DocDef) -> Result<(), MTTError> {
self.mtt.create_document(docdef)
pub fn get_morethantext(&self) -> MoreThanText {
self.mtt.clone()
}
pub fn send_time_pulse(&self) {
@@ -230,7 +278,7 @@ impl TestMoreThanText {
self.queue.send(msg);
}
pub fn register_channel(&self, paths: Vec<Path>) -> Receiver<Message> {
pub fn register_channel(&mut self, paths: Vec<Path>) {
let mut queue = self.mtt.queue.clone();
let (tx, rx) = channel();
let sender_id = queue.add_sender(tx);
@@ -239,6 +287,29 @@ impl TestMoreThanText {
queue.send(Message::new(reg_msg));
rx.recv().unwrap(); // Wait for completion.
}
rx
self.channel = Some(rx);
}
pub fn recv(&self) -> Result<Message, RecvTimeoutError> {
match &self.channel {
Some(rx) => rx.recv_timeout(Duration::from_millis(500)),
None => panic!("test environment does not have a channel setup"),
}
}
pub fn get_trigger_records(&self, action: Action) -> Records {
let msg = self.recv().unwrap();
let msg_action = msg.get_action();
if action == msg_action.clone().into() {
match msg_action {
MsgAction::OnAddition(data) => data.clone(),
MsgAction::OnDelete(data) => data.clone(),
MsgAction::OnQuery(data) => data.clone(),
MsgAction::OnUpdate(data) => data.clone(),
_ => panic!("{:?} is not a trigger", action),
}
} else {
panic!("received {:?} instead of {:?} trigger", msg, action);
}
}
}

View File

@@ -55,7 +55,7 @@ async fn create_app(state: MoreThanText) -> Router {
#[allow(dead_code)]
#[derive(Clone)]
struct SessionID(Uuid);
struct SessionID(String);
impl<S> FromRequestParts<S> for SessionID
where
@@ -73,7 +73,11 @@ where
let requested = req_id.clone();
let (tx, mut rx) = channel(1);
spawn(async move {
tx.send(state.validate_session(requested)).await.unwrap();
let id = match requested {
Some(data) => state.client_with_session(data, None).session_id(),
None => state.client().session_id(),
};
tx.send(id).await.unwrap();
});
let id = rx.recv().await.unwrap();
if !req_id.is_some_and(|x| x == id.to_string()) {
@@ -84,7 +88,7 @@ where
}
async fn mtt_conn(
_sess_id: SessionID,
sess_id: SessionID,
method: Method,
path: Path<HashMap<String, String>>,
state: State<MoreThanText>,
@@ -92,6 +96,7 @@ async fn mtt_conn(
) -> impl IntoResponse {
let (tx, mut rx) = channel(1);
spawn(async move {
let client = state.client_with_session(sess_id.0, None);
match method {
Method::GET => match path.get("document") {
Some(doc) => tx.send(state.get_document(doc, "home")).await.unwrap(),

View File

@@ -1,9 +1,312 @@
pub mod wrapper;
use crate::name::NameType;
use crate::{
action::{Field, MsgAction},
name::NameType,
queue::data_director::{Include, Path, Route},
};
use uuid::Uuid;
pub trait MessageAction {
fn doc_name(&self) -> &NameType {
&NameType::None
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct MessageID {
data: Uuid,
}
impl MessageID {
pub fn new() -> Self {
Self {
data: Uuid::new_v4(),
}
}
}
#[cfg(test)]
mod message_ids {
use super::*;
#[test]
fn are_message_ids_unique() {
let id1 = MessageID::new();
let id2 = MessageID::new();
assert!(id1 != id2);
}
}
#[derive(Clone, Debug)]
pub struct Message {
msg_id: MessageID,
action: MsgAction,
route: Route,
session: Field,
}
impl Message {
pub fn new<A>(action: A) -> Self
where
A: Into<MsgAction>,
{
Self {
msg_id: MessageID::new(),
action: action.into(),
route: Route::default(),
session: Field::None,
}
}
pub fn set_id(&self, msg_id: MessageID) -> Self {
let mut output = self.clone();
output.msg_id = msg_id;
output
}
pub fn set_session(&self, session: Field) -> Self {
let mut output = self.clone();
output.session = session;
output
}
pub fn set_action<A>(&self, action: A) -> Self
where
A: Into<MsgAction>,
{
let mut output = self.clone();
output.action = action.into();
output
}
pub fn get_message_id(&self) -> &MessageID {
&self.msg_id
}
pub fn get_action(&self) -> &MsgAction {
&self.action
}
pub fn get_path(&self) -> Path {
Path::new(
Include::Just(self.msg_id.clone()),
Include::Just(self.action.doc_name().clone()),
Include::Just(self.action.clone().into()),
)
}
pub fn get_route(&self) -> Route {
self.route.clone()
}
pub fn set_route(&mut self, route: Route) {
self.route = route;
}
}
impl Default for Message {
fn default() -> Self {
Self {
msg_id: MessageID::new(),
action: MsgAction::None,
route: Route::default(),
session: Field::None,
}
}
}
impl MessageAction for Message {
fn doc_name(&self) -> &NameType {
self.get_action().doc_name()
}
}
#[cfg(test)]
mod messages {
use super::*;
use crate::{
action::{DocDef, Query, Reply},
name::{name_id_support::test_name_id, Name},
ErrorID, MTTError,
};
fn is_there_a_default_message() {
let msg = Message::default();
match msg.action {
MsgAction::None => {}
_ => panic!("should have been no action"),
}
assert_eq!(msg.session, Field::None);
}
#[test]
fn can_create_new_messsage() {
let doc_name = Name::english("something");
let qry = Query::new(doc_name.clone());
let msg = Message::new(qry);
let expected: NameType = doc_name.into();
match msg.action {
MsgAction::Query(data) => assert_eq!(data.doc_name(), &expected),
_ => unreachable!("should have been a query"),
}
match msg.session {
Field::None => {}
_ => unreachable!("should have been none"),
}
}
#[test]
fn can_id_be_set() {
let doc_name = Name::english("identification");
let qry = Query::new(doc_name.clone());
let msg_id = MessageID::new();
let msg = Message::new(qry).set_id(msg_id.clone());
assert_eq!(msg.msg_id, msg_id);
}
#[test]
fn can_session_be_set() {
let doc_name = Name::english("identification");
let qry = Query::new(doc_name.clone());
let sess_id: Field = Uuid::new_v4().into();
let msg = Message::new(qry).set_session(sess_id.clone());
assert_eq!(msg.session, sess_id);
}
#[test]
fn can_action_be_set() {
let doc_name = Name::english("action");
let expected: NameType = doc_name.clone().into();
let docdef = DocDef::new(doc_name.clone());
let qry = Query::new(doc_name.clone());
let msg = Message::new(docdef).set_action(qry);
match msg.action {
MsgAction::Query(data) => assert_eq!(data.doc_name(), &expected),
_ => unreachable!("should have been a query"),
}
}
#[test]
fn can_the_document_be_a_named_reference() {
let dts = [Name::english("one"), Name::english("two")];
for document in dts.into_iter() {
let msg = Message::new(MsgAction::Create(DocDef::new(document.clone())));
match msg.get_action() {
MsgAction::Create(_) => {}
_ => unreachable!("should have been a create document"),
}
}
}
#[test]
fn can_the_document_be_an_id() {
let document = test_name_id();
let msg = Message::new(Query::new(document.clone()));
match msg.get_action() {
MsgAction::Query(_) => {}
_ => unreachable!("should have been an access query"),
}
}
#[test]
fn do_messages_contain_routes() {
let name = Name::english("whatever");
let mut msg = Message::new(Query::new(name.clone()));
let default_route = msg.get_route();
match default_route.msg_id {
Include::Just(_) => unreachable!("should defalt to all"),
Include::All => {}
}
match default_route.doc_id {
Include::Just(_) => unreachable!("should defalt to all"),
Include::All => {}
}
match default_route.action {
Include::Just(_) => unreachable!("should defalt to all"),
Include::All => {}
}
let doc_id = test_name_id();
let route = Route::new(
Include::Just(msg.get_message_id().clone()),
Include::Just(doc_id.clone()),
Include::Just(msg.get_action().into()),
);
msg.set_route(route);
let result = msg.get_route();
match result.msg_id {
Include::Just(data) => assert_eq!(&data, msg.get_message_id()),
Include::All => unreachable!("should have message id"),
}
match result.doc_id {
Include::Just(data) => assert_eq!(data, doc_id),
Include::All => unreachable!("should have document id"),
}
match result.action {
Include::Just(data) => assert_eq!(data, msg.get_action().into()),
Include::All => unreachable!("should have action"),
}
}
#[test]
fn is_the_message_id_random() {
let mut ids: Vec<MessageID> = Vec::new();
for _ in 0..5 {
let msg = Message::new(Query::new(Name::english("test")));
let id = msg.get_message_id().clone();
assert!(!ids.contains(&id), "{:?} containts {:?}", ids, id);
ids.push(id);
}
}
#[test]
fn can_make_reply_message() {
let name = Name::english("testing");
let msg = Message::new(Query::new(name.clone()));
let responce = Reply::new(Name::english("something"));
let reply = msg.set_action(responce);
assert_eq!(reply.get_message_id(), msg.get_message_id());
match reply.get_action() {
MsgAction::Reply(_) => {}
_ => unreachable!("should have been a reply"),
}
}
#[test]
fn can_make_error_message() {
let name = Name::english("testing");
let msg = Message::new(Query::new(name.clone()));
let err_msg = Uuid::new_v4().to_string();
let result = msg.set_action(MTTError::new(ErrorID::DocumentNotFound));
assert_eq!(result.get_message_id(), msg.get_message_id());
match result.get_action() {
MsgAction::Error(data) => match data.get_error_ids().back().unwrap() {
ErrorID::DocumentNotFound => {}
_ => unreachable!("got {:?}, should have received not found", data),
},
_ => unreachable!("should have been a reply"),
}
}
#[test]
fn can_make_a_response_message() {
let doc_id = test_name_id();
let msg = Message::new(Query::new(doc_id.clone()));
let data = Uuid::new_v4().to_string();
let result1 = msg.set_action(MTTError::new(ErrorID::DocumentNotFound));
let result2 = msg.set_action(Reply::new(NameType::None));
assert_eq!(result1.get_message_id(), msg.get_message_id());
assert_eq!(result2.get_message_id(), msg.get_message_id());
let action1 = result1.get_action();
match action1 {
MsgAction::Error(err) => match err.get_error_ids().back().unwrap() {
ErrorID::DocumentNotFound => {}
_ => unreachable!("got {:?}: should have received document not found", err),
},
_ => unreachable!("got {:?}: should have received error", action1),
}
let action2 = result2.get_action();
match action2 {
MsgAction::Reply(data) => assert_eq!(data.len(), 0),
_ => unreachable!("got {:?}: should have received a reply", action2),
}
}
}

View File

@@ -1,334 +0,0 @@
use super::MessageAction;
use crate::{
action::{CalcValue, Field, FieldType, MsgAction, Operand, Query, Reply},
mtterror::{ErrorID, MTTError},
name::{NameType, Names},
queue::data_director::{Include, Path, Route},
};
use chrono::prelude::*;
use std::{
collections::{HashMap, HashSet},
time::Duration,
};
use uuid::Uuid;
#[derive(Clone, Debug)]
pub struct Message {
msg_id: Uuid,
// document_id: NameType,
action: MsgAction,
route: Route,
// session: Option<?>
}
impl Message {
pub fn new<A>(action: A) -> Self
where
A: Into<MsgAction>,
{
Self {
msg_id: Uuid::new_v4(),
action: action.into(),
route: Route::default(),
}
}
pub fn get_message_id(&self) -> &Uuid {
&self.msg_id
}
pub fn get_action(&self) -> &MsgAction {
&self.action
}
pub fn get_path(&self) -> Path {
Path::new(
Include::Just(self.msg_id.clone()),
Include::Just(self.action.doc_name().clone()),
Include::Just(self.action.clone().into()),
)
}
pub fn get_route(&self) -> Route {
self.route.clone()
}
pub fn set_route(&mut self, route: Route) {
self.route = route;
}
pub fn response<A>(&self, action: A) -> Self
where
A: Into<MsgAction>,
{
Self {
msg_id: self.msg_id.clone(),
action: action.into(),
route: Route::default(),
}
}
pub fn forward<D, A>(&self, doc_id: D, action: A) -> Self
where
D: Into<NameType>,
A: Into<MsgAction>,
{
Self {
msg_id: self.msg_id.clone(),
action: action.into(),
route: Route::default(),
}
}
}
impl MessageAction for Message {
fn doc_name(&self) -> &NameType {
self.get_action().doc_name()
}
}
#[cfg(test)]
mod messages {
use super::*;
use crate::{
action::DocDef,
name::{name_id_support::test_name_id, Name},
};
#[test]
fn can_the_document_be_a_named_reference() {
let dts = [Name::english("one"), Name::english("two")];
for document in dts.into_iter() {
let msg = Message::new(MsgAction::Create(DocDef::new(document.clone())));
match msg.get_action() {
MsgAction::Create(_) => {}
_ => unreachable!("should have been a create document"),
}
}
}
#[test]
fn can_the_document_be_an_id() {
let document = test_name_id();
let msg = Message::new(Query::new(document.clone()));
match msg.get_action() {
MsgAction::Query(_) => {}
_ => unreachable!("should have been an access query"),
}
}
#[test]
fn do_messages_contain_routes() {
let name = Name::english("whatever");
let mut msg = Message::new(Query::new(name.clone()));
let default_route = msg.get_route();
match default_route.msg_id {
Include::Just(_) => unreachable!("should defalt to all"),
Include::All => {}
}
match default_route.doc_id {
Include::Just(_) => unreachable!("should defalt to all"),
Include::All => {}
}
match default_route.action {
Include::Just(_) => unreachable!("should defalt to all"),
Include::All => {}
}
let doc_id = test_name_id();
let route = Route::new(
Include::Just(msg.get_message_id().clone()),
Include::Just(doc_id.clone()),
Include::Just(msg.get_action().into()),
);
msg.set_route(route);
let result = msg.get_route();
match result.msg_id {
Include::Just(data) => assert_eq!(&data, msg.get_message_id()),
Include::All => unreachable!("should have message id"),
}
match result.doc_id {
Include::Just(data) => assert_eq!(data, doc_id),
Include::All => unreachable!("should have document id"),
}
match result.action {
Include::Just(data) => assert_eq!(data, msg.get_action().into()),
Include::All => unreachable!("should have action"),
}
}
#[test]
fn is_the_message_id_random() {
let mut ids: Vec<Uuid> = Vec::new();
for _ in 0..5 {
let msg = Message::new(Query::new(Name::english("test")));
let id = msg.get_message_id().clone();
assert!(!ids.contains(&id), "{:?} containts {}", ids, id);
ids.push(id);
}
}
#[test]
fn can_make_reply_message() {
let name = Name::english("testing");
let msg = Message::new(Query::new(name.clone()));
let responce = Reply::new(Name::english("something"));
let reply = msg.response(responce);
assert_eq!(reply.get_message_id(), msg.get_message_id());
match reply.get_action() {
MsgAction::Reply(_) => {}
_ => unreachable!("should have been a reply"),
}
}
#[test]
fn can_make_error_message() {
let name = Name::english("testing");
let msg = Message::new(Query::new(name.clone()));
let err_msg = Uuid::new_v4().to_string();
let result = msg.response(MTTError::new(ErrorID::DocumentNotFound));
assert_eq!(result.get_message_id(), msg.get_message_id());
match result.get_action() {
MsgAction::Error(data) => match data.get_error_ids().back().unwrap() {
ErrorID::DocumentNotFound => {}
_ => unreachable!("got {:?}, should have received not found", data),
},
_ => unreachable!("should have been a reply"),
}
}
#[test]
fn can_make_a_response_message() {
let doc_id = test_name_id();
let msg = Message::new(Query::new(doc_id.clone()));
let data = Uuid::new_v4().to_string();
let result1 = msg.response(MTTError::new(ErrorID::DocumentNotFound));
let result2 = msg.response(Reply::new(NameType::None));
assert_eq!(result1.get_message_id(), msg.get_message_id());
assert_eq!(result2.get_message_id(), msg.get_message_id());
let action1 = result1.get_action();
match action1 {
MsgAction::Error(err) => match err.get_error_ids().back().unwrap() {
ErrorID::DocumentNotFound => {}
_ => unreachable!("got {:?}: should have received document not found", err),
},
_ => unreachable!("got {:?}: should have received error", action1),
}
let action2 = result2.get_action();
match action2 {
MsgAction::Reply(data) => assert_eq!(data.len(), 0),
_ => unreachable!("got {:?}: should have received a reply", action2),
}
}
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct Operation {
field_name: String,
operation: Operand,
value: Field,
}
#[allow(dead_code)]
impl Operation {
pub fn new<F>(name: String, op: Operand, value: F) -> Self
where
F: Into<Field>,
{
Self {
field_name: name,
operation: op,
value: value.into(),
}
}
fn which_field(&self) -> String {
self.field_name.clone()
}
pub fn validate(&self, field: &Field) -> bool {
self.operation.validate(field, &self.value)
}
}
#[derive(Clone, Debug)]
pub struct Document {
data: HashMap<NameType, CalcValue>,
}
impl Document {
fn new() -> Self {
Self {
data: HashMap::new(),
}
}
pub fn add_field<NT, CV>(&mut self, name: NT, field: CV)
where
CV: Into<CalcValue>,
NT: Into<NameType>,
{
self.data.insert(name.into(), field.into());
}
fn get_field<NT>(&self, name: NT) -> &CalcValue
where
NT: Into<NameType>,
{
match self.data.get(&name.into()) {
Some(data) => data,
None => &CalcValue::None,
}
}
#[allow(dead_code)]
fn get_all(&self) -> Vec<(NameType, Field)> {
let mut output = Vec::new();
for (key, value) in self.data.iter() {
output.push((key.clone(), value.get(&Field::None)));
}
output
}
pub fn iter(&self) -> impl Iterator<Item = (&NameType, &CalcValue)> {
self.data.iter()
}
}
#[cfg(test)]
mod documents {
use super::*;
use crate::name::Name;
#[test]
fn can_add_static_string() {
let mut add = Document::new();
let name = Name::english(Uuid::new_v4().to_string().as_str());
let data = Uuid::new_v4().to_string();
add.add_field(name.clone(), data.clone());
let result = add.get_field(&name);
match result {
CalcValue::Value(holder) => match holder {
Field::StaticString(result) => assert_eq!(result, &data),
_ => unreachable!("got {:?}: should have received static string", holder),
},
_ => unreachable!("got {:?}, should have been value", result),
}
}
#[test]
fn can_add_uuid() {
let mut add = Document::new();
let name = Name::english(Uuid::new_v4().to_string().as_str());
let data = Uuid::new_v4();
add.add_field(name.clone(), data.clone());
let result = add.get_field(&name);
match result {
CalcValue::Value(holder) => match holder {
Field::Uuid(result) => assert_eq!(result, &data),
_ => unreachable!("got {:?}: should have received static string", holder),
},
_ => unreachable!("got {:?}, should have been value", result),
}
}
}

View File

@@ -2,6 +2,7 @@ use crate::{
action::{Field, FieldType},
message::MessageAction,
name::{Name, NameType},
MissingTranslation,
};
use isolang::Language;
use std::{collections::VecDeque, error::Error, fmt};
@@ -19,8 +20,9 @@ pub enum ErrorID {
FieldMissingData,
FieldNameAlreadyExists,
FieldTypeExpected(FieldType),
IndexEntryAlreadyExists,
IndexEntryAlreadyExists(Field),
InvalidFieldName(Name),
MissingTranslation(Vec<MissingTranslation>),
NameAlreadyExists,
NameLanguageNotUnique,
NameNotFound(NameType),

View File

@@ -1,2 +1,4 @@
pub mod data_director;
pub mod router;
pub use router::SenderID;

View File

@@ -1,6 +1,7 @@
use super::SenderID;
use crate::{
action::{Action, MsgAction},
message::{wrapper::Message, MessageAction},
message::{Message, MessageAction, MessageID},
mtterror::MTTError,
name::{Name, NameID, NameType, Names},
queue::router::Queue,
@@ -55,18 +56,19 @@ pub enum RegMsg {
Error(MTTError),
GetNameID(Name),
Ok,
RemoveSender(Uuid),
RemoveRoute(Route),
RemoveSender(SenderID),
RouteID(RouteID),
}
#[derive(Clone, Debug)]
pub struct Register {
msg: RegMsg,
sender_id: Uuid,
sender_id: SenderID,
}
impl Register {
pub fn new(sender_id: Uuid, reg_msg: RegMsg) -> Self {
pub fn new(sender_id: SenderID, reg_msg: RegMsg) -> Self {
Self {
msg: reg_msg,
sender_id: sender_id,
@@ -77,7 +79,7 @@ impl Register {
&self.msg
}
pub fn get_sender_id(&self) -> &Uuid {
pub fn get_sender_id(&self) -> &SenderID {
&self.sender_id
}
@@ -99,13 +101,13 @@ mod registries {
#[test]
fn does_registry_store_data() {
let name_id = test_name_id();
let sender_data_id = Uuid::new_v4();
let sender_data_id = SenderID::new();
let inputs = [
RegMsg::DocumentNameID(name_id.clone()),
RegMsg::RemoveSender(sender_data_id.clone()),
];
for regmsg in inputs.iter() {
let sender_id = Uuid::new_v4();
let sender_id = SenderID::new();
let reg = Register::new(sender_id.clone(), regmsg.clone());
assert_eq!(reg.doc_name(), &NameType::None);
assert_eq!(reg.get_sender_id(), &sender_id);
@@ -120,13 +122,13 @@ mod registries {
#[derive(Clone, Debug)]
pub struct Path {
pub msg_id: Include<Uuid>,
pub msg_id: Include<MessageID>,
pub doc: Include<NameType>,
pub action: Include<Action>,
}
impl Path {
pub fn new(id: Include<Uuid>, doc: Include<NameType>, action: Include<Action>) -> Self {
pub fn new(id: Include<MessageID>, doc: Include<NameType>, action: Include<Action>) -> Self {
Self {
msg_id: id,
doc: doc,
@@ -139,7 +141,7 @@ impl Path {
NT: Into<NameType>,
{
Self {
msg_id: Include::Just(Uuid::new_v4()),
msg_id: Include::Just(MessageID::new()),
doc: Include::Just(name.into()),
action: Include::Just(action.into()),
}
@@ -182,7 +184,7 @@ mod paths {
#[test]
fn message_ids_are_unique_for_message_paths() {
let count = 10;
let mut ids: Vec<Uuid> = Vec::new();
let mut ids: Vec<MessageID> = Vec::new();
for _ in 0..count {
let path =
Path::for_message(NameType::None, &MsgAction::Show(Show::new(NameType::None)));
@@ -200,11 +202,11 @@ mod paths {
pub struct Route {
pub action: Include<Action>,
pub doc_id: Include<NameID>,
pub msg_id: Include<Uuid>,
pub msg_id: Include<MessageID>,
}
impl Route {
pub fn new(msg_id: Include<Uuid>, doc: Include<NameID>, action: Include<Action>) -> Self {
pub fn new(msg_id: Include<MessageID>, doc: Include<NameID>, action: Include<Action>) -> Self {
Self {
action: action,
doc_id: doc,
@@ -252,7 +254,7 @@ impl From<&RouteID> for Route {
pub struct RouteID {
action: Option<Action>,
doc_id: Option<NameID>,
msg_id: Option<Uuid>,
msg_id: Option<MessageID>,
}
impl From<Route> for RouteID {
@@ -275,7 +277,7 @@ impl From<Route> for RouteID {
}
struct RouteStorage {
data: HashMap<RouteID, HashSet<Uuid>>,
data: HashMap<RouteID, HashSet<SenderID>>,
}
impl RouteStorage {
@@ -285,7 +287,7 @@ impl RouteStorage {
}
}
fn add(&mut self, route: Route, sender_id: Uuid) -> RouteID {
fn add(&mut self, route: Route, sender_id: SenderID) -> RouteID {
let route_id: RouteID = route.into();
let set = match self.data.get_mut(&route_id) {
Some(result) => result,
@@ -299,7 +301,7 @@ impl RouteStorage {
route_id
}
fn remove_sender_id(&mut self, sender_id: &Uuid) {
fn remove_sender_id(&mut self, sender_id: &SenderID) {
let mut removal: Vec<RouteID> = Vec::new();
for (route_id, set) in self.data.iter_mut() {
set.remove(sender_id);
@@ -312,7 +314,24 @@ impl RouteStorage {
}
}
fn get(&self, route: Route) -> HashSet<Uuid> {
fn remove_route(&mut self, route: Route, sender: SenderID) {
let route_id: RouteID = route.into();
let mut remove = false;
match self.data.get_mut(&route_id) {
Some(store) => {
store.remove(&sender);
if store.len() == 0 {
remove = true;
}
}
None => {}
}
if remove {
self.data.remove(&route_id);
}
}
fn get(&self, route: Route) -> HashSet<SenderID> {
let mut output = HashSet::new();
for (route_id, set) in self.data.iter() {
if route == route_id.into() {
@@ -330,10 +349,10 @@ mod route_storeage {
#[test]
fn can_add_routes() {
let mut routes = RouteStorage::new();
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
let route1 = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
let route2 = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
let id1 = SenderID::new();
let id2 = SenderID::new();
let route1 = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
let route2 = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
let route_id1 = routes.add(route1.clone(), id1.clone());
let route_id2 = routes.add(route2.clone(), id2.clone());
let result1 = routes.get(route1.clone());
@@ -359,7 +378,7 @@ mod route_storeage {
#[test]
fn returns_empty_set_when_nothing_is_available() {
let routes = RouteStorage::new();
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
let result = routes.get(route);
assert_eq!(result.len(), 0);
}
@@ -368,11 +387,11 @@ mod route_storeage {
fn returns_all_entries_using_the_same_route() {
let count = 5;
let mut routes = RouteStorage::new();
let mut ids: HashSet<Uuid> = HashSet::new();
let mut ids: HashSet<SenderID> = HashSet::new();
while ids.len() < count {
ids.insert(Uuid::new_v4());
ids.insert(SenderID::new());
}
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
for id in ids.iter() {
routes.add(route.clone(), id.clone());
}
@@ -384,8 +403,8 @@ mod route_storeage {
fn routes_are_not_duplicated() {
let count = 5;
let mut routes = RouteStorage::new();
let id = Uuid::new_v4();
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
let id = SenderID::new();
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
for _ in 0..count {
routes.add(route.clone(), id.clone());
}
@@ -397,10 +416,10 @@ mod route_storeage {
#[test]
fn overlapping_routes_are_combined() {
let mut routes = RouteStorage::new();
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
let route1 = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
let route2 = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
let id1 = SenderID::new();
let id2 = SenderID::new();
let route1 = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
let route2 = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
routes.add(route1.clone(), id1.clone());
routes.add(route2.clone(), id2.clone());
let retrieve = Route::new(Include::All, Include::All, Include::All);
@@ -414,11 +433,11 @@ mod route_storeage {
fn can_remove_sender_id() {
let mut routes = RouteStorage::new();
let count = 5;
let mut ids: HashSet<Uuid> = HashSet::new();
let mut ids: HashSet<SenderID> = HashSet::new();
while ids.len() < count {
ids.insert(Uuid::new_v4());
ids.insert(SenderID::new());
}
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
for id in ids.iter() {
routes.add(route.clone(), id.clone());
}
@@ -432,12 +451,37 @@ mod route_storeage {
#[test]
fn empty_routes_are_release_memory() {
let mut routes = RouteStorage::new();
let id = Uuid::new_v4();
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
let id = SenderID::new();
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
routes.add(route.clone(), id.clone());
routes.remove_sender_id(&id);
assert_eq!(routes.data.len(), 0);
}
#[test]
fn can_route_be_removed() {
let mut routes = RouteStorage::new();
let id = SenderID::new();
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
routes.add(route.clone(), id.clone());
routes.remove_route(route.clone(), id);
assert_eq!(routes.data.len(), 0);
}
#[test]
fn can_shared_route_be_removed() {
let mut routes = RouteStorage::new();
let id1 = SenderID::new();
let id2 = SenderID::new();
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
routes.add(route.clone(), id1.clone());
routes.add(route.clone(), id2.clone());
routes.remove_route(route.clone(), id1);
assert_eq!(routes.data.len(), 1);
let ids = routes.get(route.clone());
let id = ids.iter().last().unwrap();
assert_eq!(id, &id2);
}
}
pub struct DocRegistry {
@@ -470,7 +514,7 @@ impl DocRegistry {
match msg.get_action() {
MsgAction::Register(data) => {
let id = data.get_sender_id();
let reply = msg.response(self.register_action(data));
let reply = msg.set_action(self.register_action(data));
self.queue.forward(id, reply);
}
_ => match self.path_to_route(&msg.get_path()) {
@@ -480,7 +524,7 @@ impl DocRegistry {
self.queue.forward(sender_id, msg.clone());
}
}
Err(err) => self.queue.send(msg.response(MsgAction::Error(err))),
Err(err) => self.queue.send(msg.set_action(MsgAction::Error(err))),
},
}
}
@@ -521,6 +565,11 @@ impl DocRegistry {
self.routes.remove_sender_id(sender_id);
reg.response(RegMsg::Ok)
}
RegMsg::RemoveRoute(route) => {
self.routes
.remove_route(route.clone(), reg.get_sender_id().clone());
reg.response(RegMsg::Ok)
}
_ => reg.response(RegMsg::Ok),
}
}

View File

@@ -1,5 +1,5 @@
use crate::{
message::wrapper::Message,
message::Message,
name::NameType,
queue::data_director::{DocRegistry, RegMsg, Register},
};
@@ -12,9 +12,44 @@ use std::{
};
use uuid::Uuid;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct SenderID {
data: Uuid,
}
impl SenderID {
pub fn new() -> Self {
Self {
data: Uuid::new_v4(),
}
}
fn nil() -> Self {
Self { data: Uuid::nil() }
}
}
#[cfg(test)]
mod sender_ids {
use super::*;
#[test]
fn are_sender_ids_unique() {
let id1 = SenderID::new();
let id2 = SenderID::new();
assert!(id1 != id2, "ids should be random");
}
#[test]
fn is_nil_available() {
let id = SenderID::nil();
assert_eq!(id.data, Uuid::nil());
}
}
struct Router {
doc_registry: Sender<Message>,
senders: HashMap<Uuid, Sender<Message>>,
senders: HashMap<SenderID, Sender<Message>>,
}
impl Router {
@@ -25,23 +60,23 @@ impl Router {
}
}
fn add_sender(&mut self, sender: Sender<Message>) -> Uuid {
let mut id = Uuid::new_v4();
fn add_sender(&mut self, sender: Sender<Message>) -> SenderID {
let mut id = SenderID::new();
while self.senders.contains_key(&id) {
id = Uuid::new_v4();
id = SenderID::new();
}
self.senders.insert(id.clone(), sender);
id
}
fn remove_sender(&mut self, id: &Uuid) {
let action = Register::new(Uuid::nil(), RegMsg::RemoveSender(id.clone()));
fn remove_sender(&mut self, id: &SenderID) {
let action = Register::new(SenderID::nil(), RegMsg::RemoveSender(id.clone()));
self.doc_registry.send(Message::new(action)).unwrap();
self.senders.remove(id);
}
fn forward(&self, id: &Uuid, msg: Message) {
if id == &Uuid::nil() {
fn forward(&self, id: &SenderID, msg: Message) {
if id == &SenderID::nil() {
return;
}
match self.senders.get(id) {
@@ -70,17 +105,17 @@ impl Queue {
output
}
pub fn add_sender(&mut self, sender: Sender<Message>) -> Uuid {
pub fn add_sender(&mut self, sender: Sender<Message>) -> SenderID {
let mut router = self.router.write().unwrap();
router.add_sender(sender)
}
pub fn remove_sender(&mut self, id: &Uuid) {
pub fn remove_sender(&mut self, id: &SenderID) {
let mut router = self.router.write().unwrap();
router.remove_sender(id);
}
pub fn forward(&self, id: &Uuid, msg: Message) {
pub fn forward(&self, id: &SenderID, msg: Message) {
let router = self.router.read().unwrap();
router.forward(id, msg);
}
@@ -145,14 +180,14 @@ mod routers {
fn can_forward_message() {
let mut setup = Setup::new();
let router = setup.get_router_mut();
let mut receivers: HashMap<Uuid, Receiver<Message>> = HashMap::new();
let mut receivers: HashMap<SenderID, Receiver<Message>> = HashMap::new();
for _ in 0..10 {
let (tx, rx) = channel();
let id = router.add_sender(tx);
receivers.insert(id, rx);
}
for (id, recv) in receivers.iter() {
let msg = Message::new(Query::new(Name::english(id.to_string().as_str())));
let msg = Message::new(Query::new(Name::english("something")));
router.forward(id, msg.clone());
let result = recv.recv_timeout(TIMEOUT).unwrap();
assert_eq!(result.get_message_id(), msg.get_message_id());
@@ -164,7 +199,7 @@ mod routers {
let mut setup = Setup::new();
let router = setup.get_router_mut();
let count = 10;
let mut holder: HashSet<Uuid> = HashSet::new();
let mut holder: HashSet<SenderID> = HashSet::new();
for _ in 0..count {
let (tx, _) = channel();
holder.insert(router.add_sender(tx));
@@ -176,7 +211,7 @@ mod routers {
fn can_remove_sender() {
let mut setup = Setup::new();
let router = setup.get_router_mut();
let mut receivers: HashMap<Uuid, Receiver<Message>> = HashMap::new();
let mut receivers: HashMap<SenderID, Receiver<Message>> = HashMap::new();
for _ in 0..10 {
let (tx, rx) = channel();
let id = router.add_sender(tx);
@@ -207,7 +242,7 @@ mod routers {
_ => unreachable!("got {:?}, should have been register", action),
}
for (id, recv) in receivers.iter() {
let msg = Message::new(Query::new(Name::english(id.to_string().as_str())));
let msg = Message::new(Query::new(Name::english("something")));
router.forward(id, msg.clone());
let result = recv.recv_timeout(TIMEOUT).unwrap();
assert_eq!(result.get_message_id(), msg.get_message_id());
@@ -218,7 +253,7 @@ mod routers {
fn ignores_bad_id_removals() {
let mut setup = Setup::new();
let router = setup.get_router_mut();
let removed = Uuid::new_v4();
let removed = SenderID::new();
router.remove_sender(&removed);
assert_eq!(router.senders.len(), 0, "should have no senders.");
let announce = setup.recv().unwrap();
@@ -251,7 +286,7 @@ mod queues {
struct Setup {
test_mod: Queue,
rx: Receiver<Message>,
rx_id: Uuid,
rx_id: SenderID,
}
impl Setup {

View File

@@ -1,10 +1,12 @@
mod support;
use chrono::Utc;
use morethantext::{
action::{Addition, DocDef, Field, FieldType, Query},
ErrorID, MTTError, MoreThanText, Name,
Action, CalcValue, Calculation, ErrorID, Include, IndexType, MTTError, MoreThanText, Name,
Operand, Path, TestMoreThanText,
};
use std::collections::HashSet;
use std::{collections::HashSet, time::Duration};
use support::{random_name, TestDocument};
use uuid::Uuid;
@@ -12,6 +14,7 @@ use uuid::Uuid;
fn can_new_documents_be_added() {
let count = 5;
let mut mtt = MoreThanText::new();
let client = mtt.client();
let doc_name = random_name();
let field_name = random_name();
let mut data: HashSet<Field> = HashSet::new();
@@ -19,15 +22,15 @@ fn can_new_documents_be_added() {
data.insert(i.into());
}
let mut docdef = DocDef::new(doc_name.clone());
docdef.add_field(field_name.clone(), FieldType::Integer);
mtt.create_document(docdef);
docdef.add_field(vec![field_name.clone()], FieldType::Integer);
client.create_document(docdef);
for item in data.iter() {
let mut add = Addition::new(doc_name.clone());
add.add_field(field_name.clone(), item.clone());
mtt.records(add).unwrap();
client.records(add).unwrap();
}
let qry = Query::new(doc_name.clone());
let recs = mtt.records(qry).unwrap();
let recs = client.records(qry).unwrap();
assert_eq!(recs.len(), data.len());
for rec in recs.iter() {
let result = rec.get(&field_name).unwrap();
@@ -45,41 +48,44 @@ fn can_new_documents_be_added() {
#[test]
fn does_it_error_on_a_bad_document_name() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let doc_name = Name::english("empty");
let mut expected = MTTError::new(ErrorID::NameNotFound(doc_name.clone().into()));
expected.add_parent(ErrorID::Document(doc_name.clone().into()));
let add = Addition::new(doc_name.clone());
let result = mtt.records(add).unwrap_err();
let result = client.records(add).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn does_it_error_on_bad_field_name() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let doc_name = Name::english("holder");
let field_name = Name::english("missing");
let docdef = DocDef::new(doc_name.clone());
mtt.create_document(docdef);
client.create_document(docdef);
let mut add = Addition::new(doc_name.clone());
add.add_field(field_name.clone(), "something");
let mut expected = MTTError::new(ErrorID::NameNotFound(field_name.clone().into()));
expected.add_parent(ErrorID::Field(field_name.clone().into()));
expected.add_parent(ErrorID::Document(doc_name.clone().into()));
let result = mtt.records(add).unwrap_err();
let result = client.records(add).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn does_it_error_on_bad_field_type() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Uuid]);
mtt.create_document(test_doc.get_docdef());
client.create_document(test_doc.get_docdef());
let mut add = Addition::new(test_doc.get_doc_name().clone());
add.add_field(test_doc.get_field_name(0), "something");
let mut expected = MTTError::new(ErrorID::FieldTypeExpected(FieldType::Uuid));
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().clone().into()));
let result = mtt.records(add).unwrap_err();
let result = client.records(add).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
@@ -87,13 +93,140 @@ fn does_it_error_on_bad_field_type() {
#[ignore = "requires session to store language preference"]
fn does_it_error_on_missing_fields() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Integer, FieldType::Integer]);
mtt.create_document(test_doc.get_docdef());
client.create_document(test_doc.get_docdef());
let mut add = Addition::new(test_doc.get_doc_name().clone());
add.add_field(test_doc.get_field_name(0), 1);
let mut expected = MTTError::new(ErrorID::FieldInvalidNone);
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().clone().into()));
let result = mtt.records(add).unwrap_err();
let result = client.records(add).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn can_default_values_be_used() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let ftype = FieldType::StaticString;
let test_doc = TestDocument::new(vec![ftype.clone()]);
let mut docdef = test_doc.get_docdef();
docdef.set_default(&test_doc.get_field_name(0), ftype.clone());
client.create_document(docdef);
let add = Addition::new(test_doc.get_doc_name().clone());
let results = client.records(add).unwrap();
let rec = results.iter().last().unwrap();
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), "".into());
}
#[test]
fn can_default_values_be_set() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let ftype = FieldType::StaticString;
let fdefault = Uuid::new_v4().to_string();
let test_doc = TestDocument::new(vec![ftype.clone()]);
let mut docdef = test_doc.get_docdef();
docdef.set_default(&test_doc.get_field_name(0), fdefault.clone());
client.create_document(docdef);
let add = Addition::new(test_doc.get_doc_name().clone());
let results = client.records(add).unwrap();
let rec = results.iter().last().unwrap();
assert_eq!(
rec.get(test_doc.get_field_name(0)).unwrap(),
fdefault.into()
);
}
#[test]
fn can_default_values_be_overwritten() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let ftype = FieldType::StaticString;
let fdefault = Uuid::new_v4().to_string();
let used = "something";
let test_doc = TestDocument::new(vec![ftype.clone()]);
let mut docdef = test_doc.get_docdef();
docdef.set_default(&test_doc.get_field_name(0), fdefault.clone());
client.create_document(docdef);
let mut add = Addition::new(test_doc.get_doc_name().clone());
add.add_field(test_doc.get_field_name(0), used);
let results = client.records(add).unwrap();
let rec = results.iter().last().unwrap();
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), used.into());
}
#[test]
fn can_default_values_be_calculated() {
let duration = Duration::from_secs(300);
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::DateTime]);
let mut docdef = test_doc.get_docdef();
let mut calc = Calculation::new(Operand::Add);
calc.add_value(FieldType::DateTime);
calc.add_value(duration.clone());
docdef.set_default(&test_doc.get_field_name(0), calc);
client.create_document(docdef).unwrap();
let add = Addition::new(test_doc.get_doc_name());
let start = Utc::now() + duration;
let results = client.records(add).unwrap();
let end = Utc::now() + duration;
let rec = results.iter().last().unwrap();
let field = rec.get(&test_doc.get_field_name(0)).unwrap();
assert!(field > start.into());
assert!(field < end.into());
}
#[test]
fn are_unique_indexes_maintained_with_additions() {
let data = 1;
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Integer]);
let mut docdef = test_doc.get_docdef();
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
client.create_document(docdef);
let mut add = Addition::new(test_doc.get_doc_name());
add.add_field(test_doc.get_field_name(0), data.clone());
client.records(add.clone()).unwrap();
let mut err = MTTError::new(ErrorID::IndexEntryAlreadyExists(data.into()));
err.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
err.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
let result = client.records(add).unwrap_err();
assert_eq!(result.to_string(), err.to_string());
}
#[test]
fn does_addition_send_on_query_message() {
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Integer]);
client.create_document(test_doc.get_docdef()).unwrap();
test_env.register_channel(vec![Path::new(
Include::All,
Include::Just(test_doc.get_doc_name().into()),
Include::Just(Action::OnAddition),
)]);
let mut add = Addition::new(test_doc.get_doc_name());
add.add_field(test_doc.get_field_name(0), 2);
let add_result = client.records(add).unwrap();
let trigger_result = test_env.get_trigger_records(Action::OnAddition);
assert_eq!(trigger_result.len(), add_result.len());
assert_eq!(
trigger_result
.iter()
.last()
.unwrap()
.get(test_doc.get_field_name(0))
.unwrap(),
add_result
.iter()
.last()
.unwrap()
.get(test_doc.get_field_name(0))
.unwrap()
);
}

173
tests/client_test.rs Normal file
View File

@@ -0,0 +1,173 @@
mod support;
use isolang::Language;
use morethantext::{
Action, Addition, DocDef, ErrorID, Field, FieldType, Include, MTTError, MoreThanText, Name,
Path, Query, TestMoreThanText,
};
use std::{collections::HashSet, sync::mpsc::RecvTimeoutError};
use support::setup_range;
use uuid::Uuid;
fn doc_name() -> Name {
Name::english("session")
}
fn lang_name() -> Name {
Name::english("language")
}
#[test]
fn are_session_ids_unique() {
let count = 10;
let mtt = MoreThanText::new();
let mut ids: HashSet<String> = HashSet::new();
for _ in 0..count {
let client = mtt.client();
ids.insert(client.session_id());
}
assert_eq!(ids.len(), count, "ids = {:?}", ids);
}
#[test]
fn can_existing_sessions_be_used() {
let mtt = MoreThanText::new();
let client1 = mtt.client();
let id = client1.session_id();
drop(client1);
let client2 = mtt.client_with_session(id.clone(), None);
assert_eq!(client2.session_id(), id);
}
#[test]
fn does_expired_session_ids_return_new() {
let id = Uuid::new_v4().to_string();
let mtt = MoreThanText::new();
let client = mtt.client_with_session(id.clone(), None);
assert_ne!(client.session_id(), id);
}
#[test]
fn does_bad_id_string_get_new() {
let id = "Not uuid".to_string();
let mtt = MoreThanText::new();
let client = mtt.client_with_session(id.clone(), None);
assert_ne!(client.session_id(), id);
}
#[test]
fn can_new_clients_set_langauge() {
let lang = Language::from_639_1("fr").unwrap();
let mut test_env = TestMoreThanText::new();
let mtt = test_env.get_morethantext();
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnAddition),
);
test_env.register_channel(vec![path]);
mtt.client_with_language(lang.clone());
let result = test_env.get_trigger_records(Action::OnAddition);
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(&lang_name()).unwrap(), lang.into());
}
#[test]
fn is_lanaguage_set_for_expired_session() {
let lang = Language::from_639_1("fr").unwrap();
let mut test_env = TestMoreThanText::new();
let mtt = test_env.get_morethantext();
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnAddition),
);
test_env.register_channel(vec![path]);
mtt.client_with_session(Uuid::new_v4().to_string(), Some(lang));
let result = test_env.get_trigger_records(Action::OnAddition);
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(&lang_name()).unwrap(), lang.into());
}
#[test]
fn is_lanaguage_set_for_bad_session() {
let lang = Language::from_639_1("de").unwrap();
let mut test_env = TestMoreThanText::new();
let mtt = test_env.get_morethantext();
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnAddition),
);
test_env.register_channel(vec![path]);
mtt.client_with_session("bad".to_string(), Some(lang));
let result = test_env.get_trigger_records(Action::OnAddition);
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(&lang_name()).unwrap(), lang.into());
}
#[test]
fn do_existing_sessions_keep_language_unchanged() {
let lang1 = Language::from_639_1("de").unwrap();
let lang2 = Language::from_639_1("fr").unwrap();
let mut test_env = TestMoreThanText::new();
let mtt = test_env.get_morethantext();
let client = mtt.client_with_language(lang1);
let id = client.session_id();
drop(client);
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnUpdate),
);
test_env.register_channel(vec![path]);
mtt.client_with_session(id.clone(), Some(lang2));
let result = test_env.get_trigger_records(Action::OnUpdate);
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(&lang_name()).unwrap(), lang1.into());
}
#[test]
fn can_client_create_a_document() {
let doc_name = Name::english("dragon");
let docdef = DocDef::new(doc_name);
let mtt = MoreThanText::new();
let client = mtt.client();
client.create_document(docdef).unwrap();
}
#[test]
fn can_error_on_create_document() {
let doc_name = Name::english("dragon");
let docdef = DocDef::new(doc_name.clone());
let mtt = MoreThanText::new();
let client = mtt.client();
client.create_document(docdef.clone()).unwrap();
let expected = MTTError::new(ErrorID::NameAlreadyExists);
let result = client.create_document(docdef).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn can_perform_client_tasks() {
let count = 5;
let (test_env, test_doc) = setup_range(count.clone());
let qry = Query::new(test_doc.get_doc_name());
let client = test_env.get_morethantext().client();
let result = client.records(qry).unwrap();
assert_eq!(result.len(), count);
}
#[test]
fn can_error_on_client_tasks() {
let count = 5;
let (test_env, test_doc) = setup_range(count.clone());
let mut add = Addition::new(test_doc.get_doc_name());
add.add_field(test_doc.get_field_name(0), "pie");
let mut expected = MTTError::new(ErrorID::FieldTypeExpected(FieldType::Integer));
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
let client = test_env.get_morethantext().client();
let result = client.records(add).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}

187
tests/delete_test.rs Normal file
View File

@@ -0,0 +1,187 @@
mod support;
use morethantext::{
Action, Addition, CalcValue, Calculation, Delete, ErrorID, Field, FieldType, Include,
IndexType, MTTError, MoreThanText, Name, Operand, Path, Query,
};
use std::collections::HashSet;
use support::{setup_range, TestDocument};
#[test]
fn can_delete() {
let data = "fred";
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::StaticString]);
client.create_document(test_doc.get_docdef()).unwrap();
test_doc.populate(mtt.clone(), vec![data]);
let delete = Delete::new(test_doc.get_doc_name());
let result = client.records(delete).unwrap();
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), data.into());
let qry = Query::new(test_doc.get_doc_name());
let qresult = client.records(qry).unwrap();
assert_eq!(qresult.len(), 0);
}
#[test]
fn can_delete_specific() {
let selected = 1;
let (test_env, test_doc) = setup_range(3);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(selected.clone()).unwrap();
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
let mut delete = Delete::new(test_doc.get_doc_name());
delete.get_query_mut().add(test_doc.get_field_name(0), calc);
let result = client.records(delete).unwrap();
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(
rec.get(test_doc.get_field_name(0)).unwrap(),
selected.into()
);
let qry = Query::new(test_doc.get_doc_name());
let qresult = client.records(qry).unwrap();
assert_eq!(qresult.len(), 2);
let mut expected: HashSet<Field> = HashSet::new();
expected.insert(0.into());
expected.insert(2.into());
for qrec in qresult.iter() {
if !expected.remove(&qrec.get(&test_doc.get_field_name(0)).unwrap()) {
assert!(false, "{:?} should not have been stored", qrec);
}
}
assert_eq!(
expected.len(),
0,
"{:?} should have been in the query",
expected
);
}
#[test]
fn can_delete_multiple() {
let count: i128 = 5;
let selected: i128 = 2; // must be less than count and not less than 0
let bound = selected + 1;
let (test_env, test_doc) = setup_range(count.clone().try_into().unwrap());
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let mut calc = Calculation::new(Operand::GreaterThan);
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
calc.add_value(selected.clone()).unwrap();
let mut delete = Delete::new(test_doc.get_doc_name());
delete.get_query_mut().add(test_doc.get_field_name(0), calc);
let result = client.records(delete).unwrap();
assert_eq!(result.len(), 2);
let mut expected: HashSet<Field> = HashSet::new();
for i in bound..count {
expected.insert(i.into());
}
for rec in result.iter() {
if !expected.remove(&rec.get(&test_doc.get_field_name(0)).unwrap()) {
assert!(false, "{:?} should not have been deleted", rec);
}
}
assert_eq!(expected.len(), 0, "{:?} should have been deleted", expected);
let qry = Query::new(test_doc.get_doc_name());
let qresult = client.records(qry).unwrap();
assert_eq!(qresult.len(), bound.clone().try_into().unwrap());
let mut qexpected: HashSet<Field> = HashSet::new();
for i in 0..bound {
qexpected.insert(i.into());
}
for rec in qresult.iter() {
if !qexpected.remove(&rec.get(&test_doc.get_field_name(0)).unwrap()) {
assert!(false, "{:?} should not have been deleted", rec);
}
}
assert_eq!(
qexpected.len(),
0,
"{:?} should have been deleted",
qexpected
);
}
#[test]
fn does_delete_error_on_a_bad_query() {
let (test_env, test_doc) = setup_range(1);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let bad_name = Name::japanese("正しくない");
let mut delete = Delete::new(test_doc.get_doc_name());
let mut qry_calc = Calculation::new(Operand::Equal);
qry_calc.add_value(0).unwrap();
qry_calc
.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
delete.get_query_mut().add(bad_name.clone(), qry_calc);
let mut expected = MTTError::new(ErrorID::NameNotFound(bad_name.clone().into()));
expected.add_parent(ErrorID::Field(bad_name.into()));
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
let result = client.records(delete).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn does_delete_update_indexes() {
let id = "something";
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::StaticString]);
let mut docdef = test_doc.get_docdef();
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
client.create_document(docdef).unwrap();
test_doc.populate(mtt.clone(), vec![id]);
client
.records(Delete::new(test_doc.get_doc_name()))
.unwrap();
let mut add = Addition::new(test_doc.get_doc_name());
add.add_field(test_doc.get_field_name(0), id);
let result = client.records(add).unwrap();
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), id.into());
}
#[test]
fn does_delete_send_trigger() {
let selected = 2;
let (mut test_env, test_doc) = setup_range(3);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
test_env.register_channel(vec![Path::new(
Include::All,
Include::Just(test_doc.get_doc_name().into()),
Include::Just(Action::OnDelete),
)]);
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(selected.clone()).unwrap();
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
let mut delete = Delete::new(test_doc.get_doc_name());
delete.get_query_mut().add(test_doc.get_field_name(0), calc);
let delete_result = client.records(delete).unwrap();
let trigger_result = test_env.get_trigger_records(Action::OnDelete);
assert_eq!(trigger_result.len(), delete_result.len());
assert_eq!(
trigger_result
.iter()
.last()
.unwrap()
.get(test_doc.get_field_name(0))
.unwrap(),
delete_result
.iter()
.last()
.unwrap()
.get(test_doc.get_field_name(0))
.unwrap()
);
}

View File

@@ -13,9 +13,10 @@ pub static TIMEOUT: Duration = Duration::from_millis(500);
#[test]
fn are_errors_produced_for_duplicate_names() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let docdef = DocDef::new(Name::english("duplicate"));
mtt.create_document(docdef.clone()).unwrap();
match mtt.create_document(docdef) {
client.create_document(docdef.clone()).unwrap();
match client.create_document(docdef) {
Ok(_) => assert!(false, "should have failed"),
Err(err) => match err.get_error_ids().back().unwrap() {
ErrorID::NameAlreadyExists => {}
@@ -27,42 +28,45 @@ fn are_errors_produced_for_duplicate_names() {
#[test]
fn does_document_respond_to() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let doc_name = random_name();
let mut requests: Vec<UserAction> = Vec::new();
let mut requests: Vec<ClientAction> = Vec::new();
requests.push(Addition::new(doc_name.clone()).into());
requests.push(Delete::new(doc_name.clone()).into());
requests.push(Query::new(doc_name.clone()).into());
requests.push(Update::new(doc_name.clone()).into());
let docdef = DocDef::new(doc_name.clone());
mtt.create_document(docdef).unwrap();
client.create_document(docdef).unwrap();
for req in requests.iter() {
let result = mtt.records(req.clone()).unwrap();
let result = client.records(req.clone()).unwrap();
assert_eq!(result.len(), 0, "from {:?}", req);
}
}
#[test]
fn does_document_ignore_other_document_requests() {
let mut mtt = TestMoreThanText::new();
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let quiet = Name::english("quiet");
let alt = Name::english("alt");
mtt.create_document(DocDef::new(quiet.clone())).unwrap();
mtt.create_document(DocDef::new(alt.clone())).unwrap();
client.create_document(DocDef::new(quiet.clone())).unwrap();
client.create_document(DocDef::new(alt.clone())).unwrap();
let paths = vec![Path::new(
Include::All,
Include::Just(quiet.clone().into()),
Include::All,
)];
let rx = mtt.register_channel(paths);
let mut requests: Vec<UserAction> = Vec::new();
test_env.register_channel(paths);
let mut requests: Vec<ClientAction> = Vec::new();
requests.push(Addition::new(alt.clone()).into());
requests.push(Delete::new(alt.clone()).into());
requests.push(Query::new(alt.clone()).into());
requests.push(Update::new(alt.clone()).into());
for req in requests.iter() {
mtt.records(req.clone()).unwrap();
client.records(req.clone()).unwrap();
}
match rx.recv_timeout(TIMEOUT) {
match test_env.recv() {
Ok(msg) => unreachable!("got {:?} should have timed out", msg),
Err(err) => match err {
RecvTimeoutError::Timeout => {}

View File

@@ -1,32 +1,21 @@
mod support;
use morethantext::{
CalcValue, Calculation, DocDef, ErrorID, Field, FieldType, IndexType, MTTError, MoreThanText,
Name, Operand, Query,
Action, CalcValue, Calculation, DocDef, ErrorID, Field, FieldType, Include, IndexType,
MTTError, MoreThanText, Name, Operand, Path, Query, TestMoreThanText,
};
use std::collections::HashSet;
use support::TestDocument;
use support::{setup_range, TestDocument};
const COUNT: usize = 5;
fn setup_range() -> (MoreThanText, TestDocument) {
let mut mtt = MoreThanText::new();
let test_doc = TestDocument::new(vec![FieldType::Integer]);
mtt.create_document(test_doc.get_docdef()).unwrap();
let mut data: Vec<Vec<i128>> = Vec::new();
for i in 0..COUNT {
let holder: i128 = i.try_into().unwrap();
data.push(vec![holder]);
}
test_doc.populate_multiple(&mut mtt, data);
(mtt, test_doc)
}
#[test]
fn does_empty_query_get_all_documents() {
let (mut mtt, test_doc) = setup_range();
let (test_env, test_doc) = setup_range(COUNT);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let mut query = Query::new(test_doc.get_doc_name());
let result = mtt.records(query).unwrap();
let result = client.records(query).unwrap();
assert_eq!(result.len(), 5, "got {:?}", result);
let mut holder: HashSet<Field> = HashSet::new();
for rec in result.iter() {
@@ -42,7 +31,9 @@ fn does_empty_query_get_all_documents() {
#[test]
fn does_query_pull_specific_information() {
let (mut mtt, test_doc) = setup_range();
let (test_env, test_doc) = setup_range(COUNT);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let expected = 3;
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(expected.clone()).unwrap();
@@ -50,7 +41,7 @@ fn does_query_pull_specific_information() {
.unwrap();
let mut query = Query::new(test_doc.get_doc_name());
query.add(test_doc.get_field_name(0), calc);
let result = mtt.records(query).unwrap();
let result = client.records(query).unwrap();
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(
@@ -61,7 +52,9 @@ fn does_query_pull_specific_information() {
#[test]
fn does_query_work_with_less_than() {
let (mut mtt, test_doc) = setup_range();
let (test_env, test_doc) = setup_range(COUNT);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let expected = 2;
let mut calc = Calculation::new(Operand::LessThan);
calc.add_value(expected.clone()).unwrap();
@@ -69,7 +62,7 @@ fn does_query_work_with_less_than() {
.unwrap();
let mut query = Query::new(test_doc.get_doc_name());
query.add(test_doc.get_field_name(0), calc);
let result = mtt.records(query).unwrap();
let result = client.records(query).unwrap();
assert_eq!(result.len(), 2, "got {:?}", result);
let mut holder: HashSet<Field> = HashSet::new();
for rec in result.iter() {
@@ -85,7 +78,9 @@ fn does_query_work_with_less_than() {
#[test]
fn does_query_work_with_less_than_equal() {
let (mut mtt, test_doc) = setup_range();
let (test_env, test_doc) = setup_range(COUNT);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let expected = 2;
let mut calc = Calculation::new(Operand::LessThanEqual);
calc.add_value(expected.clone()).unwrap();
@@ -93,7 +88,7 @@ fn does_query_work_with_less_than_equal() {
.unwrap();
let mut query = Query::new(test_doc.get_doc_name());
query.add(test_doc.get_field_name(0), calc);
let result = mtt.records(query).unwrap();
let result = client.records(query).unwrap();
assert_eq!(result.len(), 3, "got {:?}", result);
let mut holder: HashSet<Field> = HashSet::new();
for rec in result.iter() {
@@ -109,7 +104,9 @@ fn does_query_work_with_less_than_equal() {
#[test]
fn does_query_work_with_greater_than() {
let (mut mtt, test_doc) = setup_range();
let (test_env, test_doc) = setup_range(COUNT);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let expected = 2;
let mut calc = Calculation::new(Operand::GreaterThan);
calc.add_value(expected.clone()).unwrap();
@@ -117,7 +114,7 @@ fn does_query_work_with_greater_than() {
.unwrap();
let mut query = Query::new(test_doc.get_doc_name());
query.add(test_doc.get_field_name(0), calc);
let result = mtt.records(query).unwrap();
let result = client.records(query).unwrap();
assert_eq!(result.len(), 2, "got {:?}", result);
let mut holder: HashSet<Field> = HashSet::new();
for rec in result.iter() {
@@ -132,7 +129,9 @@ fn does_query_work_with_greater_than() {
#[test]
fn does_query_work_with_greater_than_equal() {
let (mut mtt, test_doc) = setup_range();
let (test_env, test_doc) = setup_range(COUNT);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let expected = 2;
let mut calc = Calculation::new(Operand::GreaterThanEqual);
calc.add_value(expected.clone()).unwrap();
@@ -140,7 +139,7 @@ fn does_query_work_with_greater_than_equal() {
.unwrap();
let mut query = Query::new(test_doc.get_doc_name());
query.add(test_doc.get_field_name(0), calc);
let result = mtt.records(query).unwrap();
let result = client.records(query).unwrap();
assert_eq!(result.len(), 3, "got {:?}", result);
let mut holder: HashSet<Field> = HashSet::new();
for rec in result.iter() {
@@ -156,15 +155,16 @@ fn does_query_work_with_greater_than_equal() {
#[test]
fn can_query_use_multiple_fields() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::StaticString, FieldType::StaticString]);
mtt.create_document(test_doc.get_docdef()).unwrap();
client.create_document(test_doc.get_docdef()).unwrap();
let input = vec![
vec!["a", "a"],
vec!["a", "b"],
vec!["b", "a"],
vec!["b", "b"],
];
test_doc.populate_multiple(&mut mtt, input);
test_doc.populate_multiple(mtt.clone(), input);
let mut calc1 = Calculation::new(Operand::Equal);
calc1.add_value("a").unwrap();
calc1
@@ -178,7 +178,7 @@ fn can_query_use_multiple_fields() {
let mut query = Query::new(test_doc.get_doc_name());
query.add(test_doc.get_field_name(0), calc1);
query.add(test_doc.get_field_name(1), calc2);
let results = mtt.records(query).unwrap();
let results = client.records(query).unwrap();
assert_eq!(results.len(), 1, "got {:?}", results);
let rec = results.iter().last().unwrap();
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), "a".into());
@@ -188,6 +188,7 @@ fn can_query_use_multiple_fields() {
#[test]
fn can_query_use_multiple_indexed_fields() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::StaticString, FieldType::StaticString]);
let mut docdef = test_doc.get_docdef();
docdef
@@ -196,14 +197,14 @@ fn can_query_use_multiple_indexed_fields() {
docdef
.add_index(&test_doc.get_field_name(1), IndexType::Index)
.unwrap();
mtt.create_document(docdef).unwrap();
client.create_document(docdef).unwrap();
let input = vec![
vec!["a", "a"],
vec!["a", "b"],
vec!["b", "a"],
vec!["b", "b"],
];
test_doc.populate_multiple(&mut mtt, input);
test_doc.populate_multiple(mtt.clone(), input);
let mut calc1 = Calculation::new(Operand::Equal);
calc1.add_value("a").unwrap();
calc1
@@ -217,7 +218,7 @@ fn can_query_use_multiple_indexed_fields() {
let mut query = Query::new(test_doc.get_doc_name());
query.add(test_doc.get_field_name(0), calc1);
query.add(test_doc.get_field_name(1), calc2);
let results = mtt.records(query).unwrap();
let results = client.records(query).unwrap();
assert_eq!(results.len(), 1, "got {:?}", results);
let rec = results.iter().last().unwrap();
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), "a".into());
@@ -227,19 +228,20 @@ fn can_query_use_multiple_indexed_fields() {
#[test]
fn can_query_use_multiple_mixed_index_fields() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::StaticString, FieldType::StaticString]);
let mut docdef = test_doc.get_docdef();
docdef
.add_index(&test_doc.get_field_name(0), IndexType::Index)
.unwrap();
mtt.create_document(docdef).unwrap();
client.create_document(docdef).unwrap();
let input = vec![
vec!["a", "a"],
vec!["a", "b"],
vec!["b", "a"],
vec!["b", "b"],
];
test_doc.populate_multiple(&mut mtt, input);
test_doc.populate_multiple(mtt.clone(), input);
let mut calc1 = Calculation::new(Operand::Equal);
calc1.add_value("a").unwrap();
calc1
@@ -253,7 +255,7 @@ fn can_query_use_multiple_mixed_index_fields() {
let mut query = Query::new(test_doc.get_doc_name());
query.add(test_doc.get_field_name(0), calc1);
query.add(test_doc.get_field_name(1), calc2);
let results = mtt.records(query).unwrap();
let results = client.records(query).unwrap();
assert_eq!(results.len(), 1, "got {:?}", results);
let rec = results.iter().last().unwrap();
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), "a".into());
@@ -263,10 +265,11 @@ fn can_query_use_multiple_mixed_index_fields() {
#[test]
fn does_it_error_on_bad_field_name() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let doc_name = Name::english("holder");
let field_name = Name::english("missing");
let docdef = DocDef::new(doc_name.clone());
mtt.create_document(docdef);
client.create_document(docdef);
let mut calc = Calculation::new(Operand::Equal);
calc.add_value("a").unwrap();
calc.add_value(CalcValue::Existing(FieldType::StaticString))
@@ -276,15 +279,16 @@ fn does_it_error_on_bad_field_name() {
let mut expected = MTTError::new(ErrorID::NameNotFound(field_name.clone().into()));
expected.add_parent(ErrorID::Field(field_name.clone().into()));
expected.add_parent(ErrorID::Document(doc_name.clone().into()));
let result = mtt.records(qry).unwrap_err();
let result = client.records(qry).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn does_it_error_on_bad_field_type() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Uuid]);
mtt.create_document(test_doc.get_docdef());
client.create_document(test_doc.get_docdef());
let mut calc = Calculation::new(Operand::Equal);
calc.add_value("a").unwrap();
calc.add_value(CalcValue::Existing(FieldType::StaticString))
@@ -294,6 +298,42 @@ fn does_it_error_on_bad_field_type() {
let mut expected = MTTError::new(ErrorID::FieldTypeExpected(FieldType::Uuid));
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().clone().into()));
let result = mtt.records(qry).unwrap_err();
let result = client.records(qry).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn does_query_send_on_query_message() {
let selected = 2;
let (mut test_env, test_doc) = setup_range(3);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
test_env.register_channel(vec![Path::new(
Include::All,
Include::Just(test_doc.get_doc_name().into()),
Include::Just(Action::OnQuery),
)]);
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(selected.clone()).unwrap();
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
let mut qry = Query::new(test_doc.get_doc_name());
qry.add(test_doc.get_field_name(0), calc);
let query_result = client.records(qry).unwrap();
let trigger_result = test_env.get_trigger_records(Action::OnQuery);
assert_eq!(trigger_result.len(), query_result.len());
assert_eq!(
trigger_result
.iter()
.last()
.unwrap()
.get(test_doc.get_field_name(0))
.unwrap(),
query_result
.iter()
.last()
.unwrap()
.get(test_doc.get_field_name(0))
.unwrap()
);
}

View File

@@ -1,23 +1,39 @@
use chrono::{DateTime, Utc};
use isolang::Language;
use morethantext::{
action::{Addition, CalcValue, Calculation, Field, FieldType, Operand, Query, Record},
MTTError, MoreThanText, Name, TestMoreThanText, Update,
Action, ErrorID, Include, MTTError, MoreThanText, Name, Path, TestMoreThanText, Update,
};
use std::time::Duration;
use uuid::Uuid;
const DELAY: Duration = Duration::from_hours(1);
fn doc_name() -> Name {
Name::english("session")
}
fn id_name() -> Name {
Name::english("id")
}
fn expire_name() -> Name {
Name::english("expire")
}
fn lang_name() -> Name {
Name::english("language")
}
fn get_session(mtt: &mut MoreThanText, id: &Uuid) -> Result<Record, MTTError> {
let client = mtt.client();
let mut qry = Query::new(doc_name());
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(CalcValue::Existing(FieldType::Uuid))
.unwrap();
calc.add_value(id.clone()).unwrap();
qry.add(Name::english("id"), calc.clone());
match mtt.records(qry) {
match client.records(qry) {
Ok(data) => Ok(data.iter().last().unwrap()),
Err(err) => Err(err),
}
@@ -27,9 +43,9 @@ fn get_session(mtt: &mut MoreThanText, id: &Uuid) -> Result<Record, MTTError> {
fn are_session_ids_unique() {
let mut mtt = MoreThanText::new();
let count = 10;
let mut result: Vec<Uuid> = Vec::new();
let mut result: Vec<String> = Vec::new();
for _ in 0..count {
let id = mtt.validate_session(None);
let id = mtt.client().session_id();
assert!(!result.contains(&id), "found {} in {:?}", id, result);
result.push(id);
}
@@ -37,99 +53,184 @@ fn are_session_ids_unique() {
#[test]
fn bad_session_id_returns_new_id() {
let mut mtt = MoreThanText::new();
let id1 = mtt.validate_session(Some("stuff".to_string()));
let id2 = mtt.validate_session(Some("stuff".to_string()));
assert_ne!(id1, id2);
let id = "stuff".to_string();
let result = MoreThanText::new().client().session_id();
assert_ne!(result, id);
}
#[test]
fn creates_new_session_if_bad_or_expired() {
let mut mtt = MoreThanText::new();
let id1 = mtt.validate_session(Some(Uuid::nil().to_string()));
let id2 = mtt.validate_session(Some(Uuid::nil().to_string()));
assert_ne!(id1, id2);
fn creates_new_session_if_missing_or_expired() {
let id = Uuid::nil().to_string();
let result = MoreThanText::new()
.client_with_session(id.clone(), None)
.session_id();
assert_ne!(result, id);
}
#[test]
fn returns_same_session_id_when_valid() {
let mut mtt = MoreThanText::new();
let id = mtt.validate_session(None);
let result = mtt.validate_session(Some(id.to_string()));
assert_eq!(result, id);
let expected = mtt.client().session_id();
let result = mtt.client_with_session(expected.clone(), None).session_id();
assert_eq!(result, expected);
}
#[test]
fn is_expiration_date_set_in_the_future() {
let mut mtt = MoreThanText::new();
let start_time = Utc::now() + Duration::from_hours(1);
let id = mtt.validate_session(None);
let end_time = Utc::now() + Duration::from_hours(1);
let rec = get_session(&mut mtt, &id).unwrap();
let holder = rec.get(Name::english("expire")).unwrap();
match holder {
Field::DateTime(data) => {
assert!(data > start_time, "expire should be after {:?}", start_time);
assert!(data < end_time, "expire should be before {:?}", end_time);
}
_ => unreachable!("got {:?} should have been date time", holder),
};
let mut test_env = TestMoreThanText::new();
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnAddition),
);
test_env.register_channel(vec![path]);
let start_time = Utc::now() + DELAY;
let id = test_env.get_morethantext().client();
let end_time = Utc::now() + DELAY;
let result = test_env.get_trigger_records(Action::OnAddition);
let rec = result.iter().last().unwrap();
let expire = rec.get(&expire_name()).unwrap();
assert!(
expire > start_time.into(),
"{:?} should be after {:?}",
expire,
start_time
);
assert!(
expire < end_time.into(),
"{:?} should be after {:?}",
expire,
end_time
);
}
#[test]
#[ignore = "hangs without completing"]
fn are_session_ids_unique_on_update() {
fn are_session_ids_unique_in_storage() {
let mut mtt = MoreThanText::new();
let id = mtt.validate_session(None);
let client = mtt.client();
let id: Uuid = mtt.client().session_id().try_into().unwrap();
let mut addition = Addition::new(doc_name());
addition.add_field(Name::english("id"), id);
match mtt.records(addition) {
addition.add_field(id_name(), id);
let mut error = MTTError::new(ErrorID::IndexEntryAlreadyExists(id.into()));
error.add_parent(ErrorID::Field(id_name().into()));
error.add_parent(ErrorID::Document(doc_name().into()));
match client.records(addition) {
Ok(data) => unreachable!("got {:?} should have been error", data),
Err(err) => match err {
_ => unreachable!("got {:?}, should have been not unique", err),
},
Err(err) => assert_eq!(err.to_string(), error.to_string()),
}
}
#[test]
fn does_expire_update_on_query() {
let mut mtt = MoreThanText::new();
let id = mtt.validate_session(None);
let start_time = Utc::now() + Duration::from_secs(3600);
mtt.validate_session(Some(id.to_string()));
let end_time = Utc::now() + Duration::from_secs(3601);
let rec = get_session(&mut mtt, &id).unwrap();
let holder = rec.get(Name::english("expire")).unwrap();
match holder {
Field::DateTime(data) => {
assert!(data > start_time, "expire should be after {:?}", start_time);
assert!(data < end_time, "expire should be before {:?}", end_time);
}
_ => unreachable!("got {:?} should have been date time", holder),
}
fn does_expire_updates_on_query() {
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let id = mtt.client().session_id();
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnUpdate),
);
test_env.register_channel(vec![path]);
let start_time = Utc::now() + DELAY;
mtt.client_with_session(id, None);
let result = test_env.get_trigger_records(Action::OnUpdate);
let rec = result.iter().last().unwrap();
let expire = rec.get(&expire_name()).unwrap();
assert!(
expire > start_time.into(),
"{:?} should be after {:?}",
expire,
start_time
);
}
#[test]
#[ignore = "failing to update"]
fn are_expired_sessions_removed() {
let mut mtt = TestMoreThanText::new();
let id = mtt.validate_session(None);
let mut update = Update::new(doc_name());
let mut test_env = TestMoreThanText::new();
let client = test_env.get_morethantext().client();
let id: Uuid = test_env
.get_morethantext()
.client()
.session_id()
.try_into()
.unwrap();
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(CalcValue::Existing(FieldType::Uuid))
.unwrap();
calc.add_value(id.clone()).unwrap();
update
.get_query_mut()
.add(Name::english("id"), calc.clone());
let mut update = Update::new(doc_name());
let expire = Utc::now() - Duration::from_secs(10);
update
.get_values_mut()
.add_field(Name::english("expire"), expire);
mtt.records(update).unwrap();
mtt.send_time_pulse();
let mut qry = Query::new(doc_name());
qry.add(Name::english("id"), calc.clone());
let result = mtt.records(qry).unwrap();
assert_eq!(result.len(), 0);
update.get_query_mut().add(id_name(), calc.clone());
update.get_values_mut().add_field(expire_name(), expire);
client.records(update).unwrap();
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnDelete),
);
test_env.register_channel(vec![path]);
test_env.send_time_pulse();
let result = test_env.get_trigger_records(Action::OnDelete);
assert_eq!(result.len(), 1, "incorrect number of records\n{:?}", result);
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(id_name()).unwrap(), id.into());
}
#[test]
fn is_english_the_default_language() {
let lang_name = Name::english("language");
let lang = Language::from_639_1("en").unwrap();
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnAddition),
);
test_env.register_channel(vec![path]);
mtt.client();
let result = test_env.get_trigger_records(Action::OnAddition);
assert_eq!(result.len(), 1, "incorrect number of records");
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(&lang_name).unwrap(), lang.into());
}
#[test]
fn can_language_be_assigned() {
let lang_name = Name::english("language");
let lang = Language::from_639_1("ja").unwrap();
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnAddition),
);
test_env.register_channel(vec![path]);
mtt.client_with_language(lang.clone());
let result = test_env.get_trigger_records(Action::OnAddition);
assert_eq!(result.len(), 1, "incorrect number of records");
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(&lang_name).unwrap(), lang.into());
}
#[test]
fn does_not_change_language() {
let lang_name = Name::english("language");
let elang = Language::from_639_1("en").unwrap();
let jlang = Language::from_639_1("ja").unwrap();
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let id = mtt.client_with_language(jlang.clone()).session_id();
let path = Path::new(
Include::All,
Include::Just(doc_name().into()),
Include::Just(Action::OnUpdate),
);
test_env.register_channel(vec![path]);
mtt.client_with_session(id, Some(elang));
let result = test_env.get_trigger_records(Action::OnUpdate);
assert_eq!(result.len(), 1, "incorrect number of records");
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(&lang_name).unwrap(), jlang.into());
}

View File

@@ -1,27 +1,45 @@
use morethantext::{Addition, DocDef, Field, FieldType, MoreThanText, Name};
use morethantext::{Addition, DocDef, Field, FieldType, MoreThanText, Name, TestMoreThanText};
use uuid::Uuid;
pub fn random_name() -> Name {
Name::english(Uuid::new_v4().to_string().as_str())
}
pub fn setup_range(count: usize) -> (TestMoreThanText, TestDocument) {
let test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Integer]);
client.create_document(test_doc.get_docdef()).unwrap();
let mut data: Vec<Vec<i128>> = Vec::new();
for i in 0..count {
let holder: i128 = i.try_into().unwrap();
data.push(vec![holder]);
}
test_doc.populate_multiple(mtt.clone(), data);
(test_env, test_doc)
}
pub struct TestDocument {
docdef: DocDef,
doc_name: Name,
field_names: Vec<Name>,
field_types: Vec<FieldType>,
}
impl TestDocument {
pub fn new(fields: Vec<FieldType>) -> Self {
let doc_name = random_name();
let mut docdef = DocDef::new(doc_name.clone());
let mut fnames = Vec::new();
for i in 0..fields.len() {
let name = Name::english(format!("field{}", i).as_str());
docdef.add_field(vec![name.clone()], fields[i].clone());
fnames.push(name);
}
Self {
doc_name: random_name(),
docdef: docdef,
doc_name: doc_name,
field_names: fnames,
field_types: fields,
}
}
@@ -30,18 +48,14 @@ impl TestDocument {
}
pub fn get_docdef(&self) -> DocDef {
let mut output = DocDef::new(self.doc_name.clone());
for i in 0..self.field_types.len() {
output.add_field(self.field_names[i].clone(), self.field_types[i].clone());
}
output
self.docdef.clone()
}
pub fn get_field_name(&self, position: usize) -> Name {
self.field_names[position].clone()
}
pub fn populate<F>(&self, mtt: &mut MoreThanText, data: Vec<F>)
pub fn populate<F>(&self, mut mtt: MoreThanText, data: Vec<F>)
where
F: Into<Field> + Clone,
{
@@ -49,7 +63,7 @@ impl TestDocument {
self.populate_multiple(mtt, wrapper);
}
pub fn populate_multiple<F>(&self, mtt: &mut MoreThanText, data: Vec<Vec<F>>)
pub fn populate_multiple<F>(&self, mut mtt: MoreThanText, data: Vec<Vec<F>>)
where
F: Into<Field> + Clone,
{
@@ -59,7 +73,7 @@ impl TestDocument {
let field: Field = rec[i].clone().into();
add.add_field(self.field_names[i].clone(), field);
}
mtt.records(add).unwrap();
mtt.client().records(add).unwrap();
}
}
}

View File

@@ -59,6 +59,7 @@ fn can_field_types_be_varied() {
fn can_record_be_added_to_document() {
let count = 5;
let mut mtt = MoreThanText::new();
let client = mtt.client();
let mut fields: Vec<FieldType> = Vec::new();
let mut input: Vec<String> = Vec::new();
for i in 0..count {
@@ -66,10 +67,10 @@ fn can_record_be_added_to_document() {
input.push(i.to_string());
}
let test_doc = TestDocument::new(fields);
mtt.create_document(test_doc.get_docdef());
test_doc.populate(&mut mtt, input);
client.create_document(test_doc.get_docdef());
test_doc.populate(mtt.clone(), input);
let query = Query::new(test_doc.get_doc_name());
let results = mtt.records(query).unwrap();
let results = client.records(query).unwrap();
assert_eq!(results.len(), 1);
for rec in results.iter() {
for i in 0..count {
@@ -83,16 +84,17 @@ fn can_record_be_added_to_document() {
fn can_document_be_populated_with_multiple_records() {
let count = 5;
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Integer]);
mtt.create_document(test_doc.get_docdef());
client.create_document(test_doc.get_docdef());
let mut data: Vec<Vec<i128>> = Vec::new();
for i in 0..count {
let item: i128 = i.try_into().unwrap();
let holder: Vec<i128> = vec![item];
data.push(holder);
}
test_doc.populate_multiple(&mut mtt, data);
test_doc.populate_multiple(mtt.clone(), data);
let query = Query::new(test_doc.get_doc_name());
let results = mtt.records(query).unwrap();
let results = client.records(query).unwrap();
assert_eq!(results.len(), count);
}

232
tests/trigger_test.rs Normal file
View File

@@ -0,0 +1,232 @@
mod support;
use morethantext::{
Action, Addition, CalcValue, Calculation, Delete, DocDef, DocFuncType, FieldType, Include,
MoreThanText, Name, Operand, Path, Query, TestMoreThanText, Update,
};
use support::TestDocument;
#[test]
fn can_a_trigger_cause_an_update() {
let data0 = 0;
let data1 = 0;
let data1_expected = 1;
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Integer, FieldType::Integer]);
let mut calc = Calculation::new(Operand::Add);
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
calc.add_value(1).unwrap();
let mut update = Update::new(test_doc.get_doc_name());
update.add_field(test_doc.get_field_name(1), calc);
let path = Path::new(
Include::All,
Include::Just(test_doc.get_doc_name().into()),
Include::Just(Action::OnQuery),
);
let function = DocFuncType::ExistingQuery(update.into());
let mut docdef = test_doc.get_docdef();
docdef.add_route(path, function);
client.create_document(docdef);
test_doc.populate(mtt.clone(), vec![data0.clone(), data1.clone()]);
let path = Path::new(
Include::All,
Include::Just(test_doc.get_doc_name().into()),
Include::Just(Action::OnUpdate),
);
test_env.register_channel(vec![path]);
let first_qry = client.records(Query::new(test_doc.get_doc_name())).unwrap();
assert_eq!(first_qry.len(), 1);
let first_rec = first_qry.iter().last().unwrap();
assert_eq!(
first_rec.get(test_doc.get_field_name(0)).unwrap(),
data0.clone().into()
);
assert_eq!(
first_rec.get(test_doc.get_field_name(1)).unwrap(),
data1.clone().into()
);
let result = test_env.get_trigger_records(Action::OnUpdate);
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(
rec.get(test_doc.get_field_name(0)).unwrap(),
data0.clone().into()
);
assert_eq!(
rec.get(test_doc.get_field_name(1)).unwrap(),
data1_expected.clone().into()
);
}
#[test]
fn can_trigger_update_specific_record() {
let count = 3;
let selected = 1; // must be greater than or equal to 0 and less than count
let initial_data = 0;
let expected = 1;
let mut input: Vec<Vec<i128>> = Vec::new();
for i in 0..count {
input.push(vec![i.clone(), initial_data.clone()]);
}
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Integer, FieldType::Integer]);
let mut calc = Calculation::new(Operand::Add);
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
calc.add_value(1).unwrap();
let mut update = Update::new(test_doc.get_doc_name());
update.add_field(test_doc.get_field_name(1), calc);
let path = Path::new(
Include::All,
Include::Just(test_doc.get_doc_name().into()),
Include::Just(Action::OnQuery),
);
let function = DocFuncType::ExistingQuery(update.into());
let mut docdef = test_doc.get_docdef();
docdef.add_route(path, function);
client.create_document(docdef);
test_doc.populate_multiple(mtt.clone(), input);
let mut qry_calc = Calculation::new(Operand::Equal);
qry_calc
.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
qry_calc.add_value(selected.clone()).unwrap();
let mut qry = Query::new(test_doc.get_doc_name());
qry.add(test_doc.get_field_name(0), qry_calc);
let path = Path::new(
Include::All,
Include::Just(test_doc.get_doc_name().into()),
Include::Just(Action::OnUpdate),
);
test_env.register_channel(vec![path]);
let first_result = client.records(qry).unwrap();
assert_eq!(first_result.len(), 1);
let first_rec = first_result.iter().last().unwrap();
assert_eq!(
first_rec.get(test_doc.get_field_name(0)).unwrap(),
selected.clone().into()
);
assert_eq!(
first_rec.get(test_doc.get_field_name(1)).unwrap(),
initial_data.clone().into()
);
let result = test_env.get_trigger_records(Action::OnUpdate);
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(
rec.get(test_doc.get_field_name(0)).unwrap(),
selected.clone().into()
);
assert_eq!(
rec.get(test_doc.get_field_name(1)).unwrap(),
expected.clone().into()
);
}
#[test]
fn can_a_trigger_from_another_document_be_used() {
let count = 3;
let selected = 1; // must be greater than or equal to 0 and less than count
let mut input: Vec<Vec<i128>> = Vec::new();
for i in 0..count {
input.push(vec![i]);
}
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Integer]);
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
calc.add_value(1).unwrap();
let mut delete = Delete::new(test_doc.get_doc_name());
delete.get_query_mut().add(test_doc.get_field_name(0), calc);
let path = Path::new(
Include::All,
Include::Just(Name::english("clock").into()),
Include::Just(Action::OnUpdate),
);
let function = DocFuncType::Trigger(delete.into());
let mut docdef = test_doc.get_docdef();
docdef.add_route(path, function);
client.create_document(docdef);
test_doc.populate_multiple(mtt.clone(), input);
let path = Path::new(
Include::All,
Include::Just(test_doc.get_doc_name().into()),
Include::Just(Action::OnDelete),
);
test_env.register_channel(vec![path]);
test_env.send_time_pulse();
let result = test_env.get_trigger_records(Action::OnDelete);
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(
rec.get(test_doc.get_field_name(0)).unwrap(),
selected.into()
);
}
#[test]
fn can_triggers_work_with_multiple_languages() {
let initial_data = 1;
let doc_names = vec![Name::english("test"), Name::japanese("テスト")];
let field_names = vec![Name::english("something"), Name::japanese("何か")];
let mut docdef = DocDef::with_names(doc_names.clone());
docdef.add_field(field_names.clone(), FieldType::Integer);
let mut calc = Calculation::new(Operand::Add);
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
calc.add_value(1).unwrap();
let mut update = Update::new(doc_names[0].clone());
update.add_field(field_names[0].clone(), calc);
let path = Path::new(
Include::All,
Include::Just(doc_names[0].clone().into()),
Include::Just(Action::OnQuery),
);
let function = DocFuncType::ExistingQuery(update.into());
docdef.add_route(path, function);
let mut test_env = TestMoreThanText::new();
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
client.create_document(docdef).unwrap();
let mut data = Addition::new(doc_names[0].clone());
data.add_field(field_names[0].clone(), initial_data.clone());
client.records(data).unwrap();
let mut qry_calc = Calculation::new(Operand::LessThan);
qry_calc.add_value(0).unwrap();
qry_calc
.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
let path = Path::new(
Include::All,
Include::Just(doc_names[0].clone().into()),
Include::Just(Action::OnUpdate),
);
test_env.register_channel(vec![path]);
for i in 0..doc_names.len() {
let holder: i128 = i.clone().try_into().unwrap();
let mut query = Query::new(doc_names[i].clone());
query.add(field_names[i].clone(), qry_calc.clone());
let qry_result = client.records(query.clone()).unwrap();
assert_eq!(qry_result.len(), 1, "got {:?} from {:?}", qry_result, query);
let qry_rec = qry_result.iter().last().unwrap();
assert_eq!(
qry_rec.get(field_names[i].clone()).unwrap(),
(initial_data + holder).into()
);
let onupdate = test_env.get_trigger_records(Action::OnUpdate);
assert_eq!(onupdate.len(), 1, "got {:?}", onupdate);
let on_rec = onupdate.iter().last().unwrap();
assert_eq!(
on_rec.get(field_names[i].clone()).unwrap(),
(initial_data + holder + 1).into()
);
}
}

341
tests/update_test.rs Normal file
View File

@@ -0,0 +1,341 @@
mod support;
use morethantext::{
Action, Addition, CalcValue, Calculation, ErrorID, Field, FieldType, Include, IndexType,
MTTError, MoreThanText, Name, Operand, Path, Query, Records, Update,
};
use std::collections::HashSet;
use support::{setup_range, TestDocument};
use uuid::Uuid;
#[test]
fn is_it_possible_to_update_nothing() {
let count = 3;
let outside: i128 = (count + 2).try_into().unwrap();
let (test_env, test_doc) = setup_range(count);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let mut update = Update::new(test_doc.get_doc_name());
update.add_field(test_doc.get_field_name(0), 20);
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(outside.clone()).unwrap();
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
update.get_query_mut().add(test_doc.get_field_name(0), calc);
let result = client.records(update).unwrap();
assert_eq!(result.len(), 0);
}
#[test]
fn does_it_update_information() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Uuid, FieldType::StaticString]);
client.create_document(test_doc.get_docdef());
let field0 = Uuid::new_v4();
let field1 = "new";
let mut input: Vec<Field> = Vec::new();
input.push(field0.clone().into());
input.push("old".into());
test_doc.populate(mtt.clone(), input);
let mut update = Update::new(test_doc.get_doc_name());
update.add_field(test_doc.get_field_name(1), field1);
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(field0.clone()).unwrap();
calc.add_value(CalcValue::Existing(FieldType::Uuid))
.unwrap();
update.get_query_mut().add(test_doc.get_field_name(0), calc);
let tester = |result: Records| {
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(
rec.get(test_doc.get_field_name(0)).unwrap(),
field0.clone().into()
);
assert_eq!(rec.get(test_doc.get_field_name(1)).unwrap(), field1.into());
};
tester(client.records(update).unwrap());
tester(client.records(Query::new(test_doc.get_doc_name())).unwrap());
}
#[test]
fn are_the_updates_limited_to_the_queried() {
let count = 3;
let choice = 1;
let changed = 20;
let (test_env, test_doc) = setup_range(count);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let mut update = Update::new(test_doc.get_doc_name());
update.add_field(test_doc.get_field_name(0), changed.clone());
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(choice.clone()).unwrap();
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
update.get_query_mut().add(test_doc.get_field_name(0), calc);
let result = client.records(update).unwrap();
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), changed.into());
let mut expected: HashSet<Field> = HashSet::new();
expected.insert(0.into());
expected.insert(2.into());
expected.insert(20.into());
let docs = client.records(Query::new(test_doc.get_doc_name())).unwrap();
for doc_data in docs.iter() {
let id = doc_data.get(test_doc.get_field_name(0)).unwrap();
expected.remove(&id);
}
assert_eq!(
expected.len(),
0,
"missed {:?} from docs: {:?}",
expected,
docs
);
}
#[test]
fn can_multiple_documents_be_update() {
let count = 3;
let choice = 1;
let (test_env, test_doc) = setup_range(count);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let mut add_up = Calculation::new(Operand::Add);
add_up
.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
add_up.add_value(20);
let mut update = Update::new(test_doc.get_doc_name());
update.add_field(test_doc.get_field_name(0), add_up);
let mut calc = Calculation::new(Operand::GreaterThanEqual);
calc.add_value(choice.clone()).unwrap();
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
update.get_query_mut().add(test_doc.get_field_name(0), calc);
let result = client.records(update).unwrap();
assert_eq!(result.len(), 2);
for rec in result.iter() {
assert!(rec.get(test_doc.get_field_name(0)).unwrap() > 10.into());
}
let mut expected: HashSet<Field> = HashSet::new();
expected.insert(2.into());
expected.insert(20.into());
expected.insert(21.into());
let docs = client.records(Query::new(test_doc.get_doc_name())).unwrap();
for doc_data in docs.iter() {
let id = doc_data.get(test_doc.get_field_name(0)).unwrap();
expected.remove(&id);
}
assert_eq!(
expected.len(),
0,
"missed {:?} from docs: {:?}",
expected,
docs
);
}
#[test]
fn does_update_error_on_a_bad_query() {
let (test_env, test_doc) = setup_range(1);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let bad_name = Name::japanese("正しくない");
let mut update = Update::new(test_doc.get_doc_name());
let mut qry_calc = Calculation::new(Operand::Equal);
qry_calc.add_value(0).unwrap();
qry_calc
.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
update.get_query_mut().add(bad_name.clone(), qry_calc);
update.add_field(test_doc.get_field_name(0), 5);
let mut expected = MTTError::new(ErrorID::NameNotFound(bad_name.clone().into()));
expected.add_parent(ErrorID::Field(bad_name.into()));
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
let result = client.records(update).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn does_update_error_on_a_bad_field_name() {
let (test_env, test_doc) = setup_range(1);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let bad_name = Name::japanese("正しくない");
let mut update = Update::new(test_doc.get_doc_name());
let mut qry_calc = Calculation::new(Operand::Equal);
qry_calc.add_value(0).unwrap();
qry_calc
.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
update
.get_query_mut()
.add(test_doc.get_field_name(0), qry_calc);
update.add_field(bad_name.clone(), 5);
let mut expected = MTTError::new(ErrorID::NameNotFound(bad_name.clone().into()));
expected.add_parent(ErrorID::Field(bad_name.into()));
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
let result = client.records(update).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn does_update_error_on_a_bad_field_type() {
let (test_env, test_doc) = setup_range(1);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
let mut update = Update::new(test_doc.get_doc_name());
let mut qry_calc = Calculation::new(Operand::Equal);
qry_calc.add_value(0).unwrap();
qry_calc
.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
update
.get_query_mut()
.add(test_doc.get_field_name(0), qry_calc);
update.add_field(test_doc.get_field_name(0), "wrong type");
let mut expected = MTTError::new(ErrorID::FieldTypeExpected(FieldType::Integer));
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
let result = client.records(update).unwrap_err();
assert_eq!(result.to_string(), expected.to_string());
}
#[test]
fn does_update_maintain_unique_index() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::StaticString]);
let mut docdef = test_doc.get_docdef();
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
client.create_document(docdef);
let old_data = "old";
let new_data = "new";
test_doc.populate(mtt.clone(), vec![old_data]);
let mut update = Update::new(test_doc.get_doc_name());
update.add_field(test_doc.get_field_name(0), new_data);
client.records(update).unwrap();
let result = client.records(Query::new(test_doc.get_doc_name())).unwrap();
assert_eq!(result.len(), 1);
let rec = result.iter().last().unwrap();
assert_eq!(
rec.get(test_doc.get_field_name(0)).unwrap(),
new_data.into()
);
let mut add_new = Addition::new(test_doc.get_doc_name());
add_new.add_field(test_doc.get_field_name(0), new_data);
let mut err = MTTError::new(ErrorID::IndexEntryAlreadyExists(new_data.into()));
err.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
err.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
let error = client.records(add_new).unwrap_err();
assert_eq!(error.to_string(), err.to_string());
let mut add_old = Addition::new(test_doc.get_doc_name());
add_old.add_field(test_doc.get_field_name(0), old_data);
let result = client.records(add_old).unwrap();
assert_eq!(result.len(), 1);
let add_rec = result.iter().last().unwrap();
assert_eq!(
add_rec.get(test_doc.get_field_name(0)).unwrap(),
old_data.into()
);
}
#[test]
fn does_index_remain_unchanged_on_update_failure() {
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::StaticString, FieldType::StaticString]);
let mut docdef = test_doc.get_docdef();
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
client.create_document(docdef);
let id = "one";
test_doc.populate(mtt.clone(), vec!["one", "data"]);
let mut update = Update::new(test_doc.get_doc_name());
update.add_field(test_doc.get_field_name(0), "two");
update.add_field(test_doc.get_field_name(1), 2);
client.records(update).unwrap_err();
let mut add = Addition::new(test_doc.get_doc_name());
add.add_field(test_doc.get_field_name(0), id);
add.add_field(test_doc.get_field_name(1), "something");
let mut err = MTTError::new(ErrorID::IndexEntryAlreadyExists(id.into()));
err.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
err.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
let error = client.records(add).unwrap_err();
assert_eq!(error.to_string(), err.to_string());
}
#[test]
#[ignore = "requires session to hold language information"]
fn does_update_error_when_it_overrides_unique_index() {
let count = 3;
let mut mtt = MoreThanText::new();
let client = mtt.client();
let test_doc = TestDocument::new(vec![FieldType::Integer]);
let mut docdef = test_doc.get_docdef();
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
client.create_document(docdef);
let mut input: Vec<Vec<i128>> = Vec::new();
for i in 0..count {
input.push(vec![i]);
}
test_doc.populate_multiple(mtt.clone(), input);
let new_data = 5;
let mut update = Update::new(test_doc.get_doc_name());
update.add_field(test_doc.get_field_name(0), new_data.clone());
let mut err = MTTError::new(ErrorID::IndexEntryAlreadyExists(new_data.into()));
err.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
err.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
let result = client.records(update).unwrap_err();
assert_eq!(result.to_string(), err.to_string());
for i in 0..count {
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(i.clone()).unwrap();
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
let mut qry = Query::new(test_doc.get_doc_name());
qry.add(test_doc.get_field_name(0), calc);
let holder = client.records(qry).unwrap();
assert_eq!(holder.len(), 1);
let rec = holder.iter().last().unwrap();
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), i.into());
}
}
#[test]
fn does_update_send_on_update_message() {
let selected = 2;
let (mut test_env, test_doc) = setup_range(3);
let mut mtt = test_env.get_morethantext();
let client = mtt.client();
test_env.register_channel(vec![Path::new(
Include::All,
Include::Just(test_doc.get_doc_name().into()),
Include::Just(Action::OnUpdate),
)]);
let mut calc = Calculation::new(Operand::Equal);
calc.add_value(selected.clone()).unwrap();
calc.add_value(CalcValue::Existing(FieldType::Integer))
.unwrap();
let mut update = Update::new(test_doc.get_doc_name());
update.get_query_mut().add(test_doc.get_field_name(0), calc);
update.add_field(test_doc.get_field_name(0), 5);
let update_result = client.records(update).unwrap();
let trigger_result = test_env.get_trigger_records(Action::OnUpdate);
assert_eq!(trigger_result.len(), update_result.len());
assert_eq!(
trigger_result
.iter()
.last()
.unwrap()
.get(test_doc.get_field_name(0))
.unwrap(),
update_result
.iter()
.last()
.unwrap()
.get(test_doc.get_field_name(0))
.unwrap()
);
}