This is an automated email from the ASF dual-hosted git repository.

sergeykamov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git


The following commit(s) were added to refs/heads/master by this push:
     new 8c7b018  Numeric manager improved. Alarm model tests extended.
8c7b018 is described below

commit 8c7b01874bce8eaa757450cefd9ed6150b0357dd
Author: Sergey Kamov <[email protected]>
AuthorDate: Wed Aug 4 09:49:17 2021 +0300

    Numeric manager improved. Alarm model tests extended.
---
 nlpcraft-examples/alarm/pom.xml                    |  8 ++
 .../apache/nlpcraft/examples/alarm/AlarmModel.java | 92 +++++++++++++---------
 .../alarm/src/main/resources/alarm_model.json      |  2 +-
 .../alarm/src/main/resources/alarm_samples.txt     | 13 ++-
 .../alarm/src/main/resources/intents.idl           |  2 +-
 .../nlpcraft/examples/alarm/NCAlarmModelSpec.scala | 84 ++++++++++++++++++++
 .../nlpcraft/common/nlp/numeric/NCNumeric.scala    | 37 +++++----
 .../common/nlp/numeric/NCNumericFuzzy.scala        | 65 +++++++++++++++
 .../common/nlp/numeric/NCNumericManager.scala      | 49 ++++++++----
 .../nlp/enrichers/numeric/NCNumericEnricher.scala  |  1 -
 .../scala/org/apache/nlpcraft/NCTestContext.scala  | 15 ++++
 11 files changed, 298 insertions(+), 70 deletions(-)

diff --git a/nlpcraft-examples/alarm/pom.xml b/nlpcraft-examples/alarm/pom.xml
index 84cb0dc..0f6af79 100644
--- a/nlpcraft-examples/alarm/pom.xml
+++ b/nlpcraft-examples/alarm/pom.xml
@@ -49,6 +49,14 @@
             <artifactId>junit-jupiter-engine</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>nlpcraft</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/nlpcraft-examples/alarm/src/main/java/org/apache/nlpcraft/examples/alarm/AlarmModel.java
 
b/nlpcraft-examples/alarm/src/main/java/org/apache/nlpcraft/examples/alarm/AlarmModel.java
index 6513e0a..0e15a02 100644
--- 
a/nlpcraft-examples/alarm/src/main/java/org/apache/nlpcraft/examples/alarm/AlarmModel.java
+++ 
b/nlpcraft-examples/alarm/src/main/java/org/apache/nlpcraft/examples/alarm/AlarmModel.java
@@ -17,12 +17,23 @@
 
 package org.apache.nlpcraft.examples.alarm;
 
-import org.apache.nlpcraft.model.*;
-import java.time.*;
-import java.time.format.*;
-import java.util.*;
+import org.apache.nlpcraft.model.NCIntentMatch;
+import org.apache.nlpcraft.model.NCIntentRef;
+import org.apache.nlpcraft.model.NCIntentSampleRef;
+import org.apache.nlpcraft.model.NCIntentTerm;
+import org.apache.nlpcraft.model.NCModelFileAdapter;
+import org.apache.nlpcraft.model.NCRejection;
+import org.apache.nlpcraft.model.NCResult;
+import org.apache.nlpcraft.model.NCToken;
 
-import static java.time.temporal.ChronoUnit.*;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import static java.time.temporal.ChronoUnit.MILLIS;
 
 /**
  * Alarm example data model.
@@ -57,23 +68,50 @@ public class AlarmModel extends NCModelFileAdapter {
         NCIntentMatch ctx,
         @NCIntentTerm("nums") List<NCToken> numToks
     ) {
-        long unitsCnt = numToks.stream().map(tok -> 
(String)tok.meta("num:unit")).distinct().count();
+        long ms = calculateTime(numToks);
         
-        if (unitsCnt != numToks.size())
-            throw new NCRejection("Ambiguous time units.");
+        assert ms >= 0;
     
+        timer.schedule(
+            new TimerTask() {
+                @Override
+                public void run() {
+                    System.out.println(
+                        "BEEP BEEP BEEP for: " + 
ctx.getContext().getRequest().getNormalizedText() + ""
+                    );
+                }
+            },
+            ms
+        );
+
+        return NCResult.text("Timer set for: " + 
FMT.format(LocalDateTime.now().plus(ms, MILLIS)));
+    }
+
+    @Override
+    public void onDiscard() {
+        // Clean up when model gets discarded (e.g. during testing).
+        timer.cancel();
+    }
+
+    /**
+     * Calculates time duration for given tokens.
+     *
+     * @param numToks Tokens to calculate time duration for.
+     * @return Time duration in ms.
+     */
+    public static long calculateTime(List<NCToken> numToks) {
         LocalDateTime now = LocalDateTime.now();
         LocalDateTime dt = now;
-    
+
         for (NCToken num : numToks) {
             String unit = num.meta("nlpcraft:num:unit");
-    
-            // Skip possible fractionals to simplify.
+
+            // Skip possible fractional to simplify.
             long v = ((Double)num.meta("nlpcraft:num:from")).longValue();
-            
+
             if (v <= 0)
                 throw new NCRejection("Value must be positive: " + unit);
-    
+
             switch (unit) {
                 case "second": { dt = dt.plusSeconds(v); break; }
                 case "minute": { dt = dt.plusMinutes(v); break; }
@@ -82,35 +120,13 @@ public class AlarmModel extends NCModelFileAdapter {
                 case "week": { dt = dt.plusWeeks(v); break; }
                 case "month": { dt = dt.plusMonths(v); break; }
                 case "year": { dt = dt.plusYears(v); break; }
-        
+
                 default:
-                    // It shouldn't be an assert, because 'datetime' unit can 
be extended.
+                    // It shouldn't be an assertion, because 'datetime' unit 
can be extended outside.
                     throw new NCRejection("Unsupported time unit: " + unit);
             }
         }
-    
-        long ms = now.until(dt, MILLIS);
-        
-        assert ms >= 0;
-    
-        timer.schedule(
-            new TimerTask() {
-                @Override
-                public void run() {
-                    System.out.println(
-                        "BEEP BEEP BEEP for: " + 
ctx.getContext().getRequest().getNormalizedText() + ""
-                    );
-                }
-            },
-            ms
-        );
-    
-        return NCResult.text("Timer set for: " + FMT.format(dt));
-    }
 
-    @Override
-    public void onDiscard() {
-        // Clean up when model gets discarded (e.g. during testing).
-        timer.cancel();
+        return now.until(dt, MILLIS);
     }
 }
diff --git a/nlpcraft-examples/alarm/src/main/resources/alarm_model.json 
b/nlpcraft-examples/alarm/src/main/resources/alarm_model.json
index 3d79875..9060fde 100644
--- a/nlpcraft-examples/alarm/src/main/resources/alarm_model.json
+++ b/nlpcraft-examples/alarm/src/main/resources/alarm_model.json
@@ -29,7 +29,7 @@
             "description": "Alarm token indicator.",
             "synonyms": [
                 "{ping|buzz|wake|call|hit} {me|up|me up|_}",
-                "{set|_} {my|_} {wake|wake up|_} 
{alarm|timer|clock|buzzer|call} {up|_}"
+                "{set|_} {my|_} {wake|wake up|_} 
{alarm|timer|clock|buzzer|call} {clock|_} {up|_}"
             ]
         }
     ],
diff --git a/nlpcraft-examples/alarm/src/main/resources/alarm_samples.txt 
b/nlpcraft-examples/alarm/src/main/resources/alarm_samples.txt
index 188d1d6..07ce622 100644
--- a/nlpcraft-examples/alarm/src/main/resources/alarm_samples.txt
+++ b/nlpcraft-examples/alarm/src/main/resources/alarm_samples.txt
@@ -22,4 +22,15 @@
 Ping me in 3 minutes tomorrow
 Buzz me in an hour and 15mins
 Set my alarm for 30s
-Please, wake me up in twenty five minutes!
\ No newline at end of file
+Please, wake me up in twenty-five minutes!
+Buzz me in few minutes
+Buzz me in a couple of minutes
+Wake me up in an hour
+Buzz me in a couple of hours
+Wake me up in a bit
+Set my alarm clock for an hour and 15mins
+Buzz me in one hour and 15mins
+Buzz me in 1 hour and 15mins
+Buzz me in 1h and 15mins
+Buzz me in a day, 1h and 15mins
+Buzz me in one day, 1h and 15mins
diff --git a/nlpcraft-examples/alarm/src/main/resources/intents.idl 
b/nlpcraft-examples/alarm/src/main/resources/intents.idl
index bae42ae..3c1810b 100644
--- a/nlpcraft-examples/alarm/src/main/resources/intents.idl
+++ b/nlpcraft-examples/alarm/src/main/resources/intents.idl
@@ -24,7 +24,7 @@ fragment=when
         @iseq = meta_tok('nlpcraft:num:isequalcondition') // Excludes 
conditional statements.
 
         tok_id() == 'nlpcraft:num' && @type == 'datetime' && @iseq == true
-    }[0,7]
+    }[1,7]
 
 // Intents (using fragments).
 intent=alarm
diff --git 
a/nlpcraft-examples/alarm/src/test/java/org/apache/nlpcraft/examples/alarm/NCAlarmModelSpec.scala
 
b/nlpcraft-examples/alarm/src/test/java/org/apache/nlpcraft/examples/alarm/NCAlarmModelSpec.scala
new file mode 100644
index 0000000..f2f6e66
--- /dev/null
+++ 
b/nlpcraft-examples/alarm/src/test/java/org/apache/nlpcraft/examples/alarm/NCAlarmModelSpec.scala
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.examples.alarm
+
+import org.apache.nlpcraft.examples.alarm.AlarmModel.calculateTime
+import org.apache.nlpcraft.model.{NCIntentRef, NCIntentTerm, NCResult, NCToken}
+import org.apache.nlpcraft.{NCTestContext, NCTestEnvironment}
+import org.junit.jupiter.api.Test
+
+import java.time.LocalDateTime
+import java.time.temporal.ChronoUnit.MILLIS
+import scala.jdk.CollectionConverters.SeqHasAsJava
+
+/**
+ * Model for alarm spec.
+ */
+class AlarmModelWrapper extends AlarmModel {
+    @NCIntentRef("alarm")
+    def onMatch(@NCIntentTerm("nums") numToks: List[NCToken]): NCResult =
+        NCResult.text(String.valueOf(calculateTime(numToks.asJava)))
+}
+
+/**
+ * Unit tests that checks that alarm model produces correct time calculations.
+ */
+@NCTestEnvironment(model = classOf[AlarmModelWrapper], startClient = true)
+class NCAlarmModelSpec extends NCTestContext {
+    // Checks with 1 second precision. Enough to be sure that calculation 
result is correct.
+    private def check(req: String, expectedTime: Long): Unit =
+        checkResult(req, _.toLong, (calcTime: Long) => Math.abs(expectedTime - 
calcTime) <= 1000)
+
+    @Test
+    def test(): Unit = {
+        val now = LocalDateTime.now
+
+        /**
+         *
+         * @param hours
+         * @param mins
+         * @param secs
+         * @return
+         */
+        def mkPeriod(hours: Int, mins: Int, secs: Int = 0): Long =
+            
now.until(now.plusHours(hours).plusMinutes(mins).plusSeconds(secs), MILLIS)
+
+        // Fuzzy `single`.
+        check("Buzz me in a minute or two.", mkPeriod(0, 1))
+        check("Buzz me in hour or two.", mkPeriod(1, 0))
+        check("Buzz me in an hour.", mkPeriod(1, 0))
+
+        // Fuzzy `few`.
+        check("Buzz me in few minutes.", mkPeriod(0, 2))
+        check("Buzz me in one or two minutes.", mkPeriod(0, 2))
+        check("Buzz me in one or two hours.", mkPeriod(2, 0))
+        check("Buzz me in a couple of minutes.", mkPeriod(0, 2))
+
+        // Fuzzy `bit`.
+        check("Buzz me in a bit.", mkPeriod(0, 2))
+
+        // Complex periods.
+        check("Buzz me in an hour and 15mins", mkPeriod(1, 15))
+        check("Buzz me in one hour and 15mins", mkPeriod(1, 15))
+        check("Buzz me in 1 hour and 15mins", mkPeriod(1, 15))
+        check("Buzz me in 1h and 15mins", mkPeriod(1, 15))
+
+        check("Buzz me in one day, 1h and 15mins", mkPeriod(25, 15))
+        check("Buzz me in a day, 1h and 15mins", mkPeriod(25, 15))
+    }
+}
diff --git a/nlpcraft-examples/alarm/src/main/resources/intents.idl 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala
similarity index 59%
copy from nlpcraft-examples/alarm/src/main/resources/intents.idl
copy to 
nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala
index bae42ae..75a3365 100644
--- a/nlpcraft-examples/alarm/src/main/resources/intents.idl
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumeric.scala
@@ -6,7 +6,7 @@
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *      https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,18 +15,27 @@
  * limitations under the License.
  */
 
-// Fragments (mostly for demo purposes here).
-fragment=buzz term~{tok_id() == 'x:alarm'}
-fragment=when
-    term(nums)~{
-        // Demonstrating term variables.
-        @type = meta_tok('nlpcraft:num:unittype')
-        @iseq = meta_tok('nlpcraft:num:isequalcondition') // Excludes 
conditional statements.
+package org.apache.nlpcraft.common.nlp.numeric
 
-        tok_id() == 'nlpcraft:num' && @type == 'datetime' && @iseq == true
-    }[0,7]
+import org.apache.nlpcraft.common.nlp.NCNlpSentenceToken
 
-// Intents (using fragments).
-intent=alarm
-    fragment(buzz)
-    fragment(when)
\ No newline at end of file
+/**
+  *
+  * @param name
+  * @param unitType
+  */
+case class NCNumericUnit(name: String, unitType: String)
+
+/**
+  *
+  * @param tokens
+  * @param value
+  * @param isFractional
+  * @param unit
+  */
+case class NCNumeric(
+    tokens: Seq[NCNlpSentenceToken],
+    value: Double,
+    isFractional: Boolean,
+    unit: Option[NCNumericUnit]
+)
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericFuzzy.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericFuzzy.scala
new file mode 100644
index 0000000..c8d19f1
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericFuzzy.scala
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.common.nlp.numeric
+
+import org.apache.nlpcraft.common.makro.NCMacroParser
+
+case class NCNumericFuzzyPeriod(unit: NCNumericUnit, value: Int)
+
+/**
+  * Fuzzy numeric configuration.
+  */
+object NCNumericFuzzy {
+    val NUMS: Map[String, NCNumericFuzzyPeriod] = {
+        val parser = new NCMacroParser
+
+        def make(txt: String, unit: NCNumericUnit, value: Int): Seq[(String, 
NCNumericFuzzyPeriod)] =
+            parser.expand(txt).map(_ -> NCNumericFuzzyPeriod(unit, value))
+
+        import org.apache.nlpcraft.common.nlp.numeric.{NCNumericUnit => U}
+
+        val singleDt =
+            Map(
+                "in {a|an|_} second {or two|_}" -> U("second", "datetime"),
+                "in {a|an|_} hour {or two|_}" -> U("hour", "datetime"),
+                "in {a|an|_} minute {or two|_}" -> U("minute", "datetime"),
+                "in {a|an|_} day {or two|_}" -> U("day", "datetime"),
+                "in {a|an|_} week {or two|_}" -> U("week", "datetime"),
+                "in {a|an|_} month {or two|_}" -> U("month", "datetime"),
+                "in {a|an|_} year {or two|_}" -> U("year", "datetime")
+            ).flatMap { case (txt, u) => make(txt, u, 1) }
+
+        val bitDt =
+            Map(
+                "in {a|an} bit" -> U("minute", "datetime")
+            ).flatMap { case (txt, u) => make(txt, u, 2) }
+
+        val fewDt =
+            Map(
+                "in {a|an|_} {few|couple of|one or two|two or three} seconds" 
-> U("second", "datetime"),
+                "in {a|an|_} {few|couple of|one or two|two or three} minutes" 
-> U("minute", "datetime"),
+                "in {a|an|_} {few|couple of|one or two|two or three} hours" -> 
U("hour", "datetime"),
+                "in {a|an|_} {few|couple of|one or two|two or three} days" -> 
U("day", "datetime"),
+                "in {a|an|_} {few|couple of|one or two|two or three} weeks" -> 
U("week", "datetime"),
+                "in {a|an|_} {few|couple of|one or two|two or three} months" 
-> U("month", "datetime"),
+                "in {a|an|_} {few|couple of|one or two|two or three} years" -> 
U("year", "datetime")
+            ).flatMap { case (txt, u) => make(txt, u, 2) }
+
+        singleDt ++ bitDt ++ fewDt
+    }
+}
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericManager.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericManager.scala
index e20a18b..a428ab9 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericManager.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/nlp/numeric/NCNumericManager.scala
@@ -17,26 +17,15 @@
 
 package org.apache.nlpcraft.common.nlp.numeric
 
-import java.text.{DecimalFormat, ParseException}
-import java.util.Locale
-
 import io.opencensus.trace.Span
 import org.apache.nlpcraft.common.NCService
 import org.apache.nlpcraft.common.nlp._
 import org.apache.nlpcraft.common.nlp.core.NCNlpCoreManager
 import org.apache.nlpcraft.common.util.NCUtils.mapResource
 
-case class NCNumericUnit(name: String, unitType: String)
-case class NCNumeric(
-    tokens: Seq[NCNlpSentenceToken],
-    value: Double,
-    isFractional: Boolean,
-    unit: Option[NCNumericUnit]
-)
+import java.text.{DecimalFormat, ParseException}
+import java.util.Locale
 
-/**
-  * Numeric detection manager.
-  */
 object NCNumericManager extends NCService {
     // Sets EN numeric format.
     Locale.setDefault(Locale.forLanguageTag("EN"))
@@ -119,6 +108,34 @@ object NCNumericManager extends NCService {
     }
 
     /**
+      * Tries to find special cases for numerics definition, without explicit 
numerics.
+      *
+      * Example: 'in an hour'.
+      *
+      * @param ns Sentence.
+      */
+    private def findFuzzy(ns: NCNlpSentence): Seq[NCNumeric] = {
+        val senToks: Seq[NCNlpSentenceToken] = ns.tokens.toSeq
+        val senWords: Seq[String] = senToks.map(_.normText)
+
+        NCNumericFuzzy.NUMS.map { case (txt, period) => txt.split(" ") -> 
period }.flatMap {
+            case (dtWords, dtPeriod) =>
+                senWords.indexOfSlice(dtWords) match {
+                    case -1 => None
+                    case idx =>
+                        Some(
+                            NCNumeric(
+                                tokens = senToks.slice(idx, idx + 
dtWords.length),
+                                value = dtPeriod.value,
+                                isFractional = false,
+                                unit = Some(dtPeriod.unit)
+                            )
+                        )
+                }
+        }
+    }
+
+    /**
      *
      * @param parent Optional parent span.
      */
@@ -434,7 +451,11 @@ object NCNumericManager extends NCService {
          
             val usedToks = nums.flatMap(_.tokens)
          
-            (nums ++ ns.filter(t => 
!usedToks.contains(t)).flatMap(mkSolidNumUnit)).sortBy(_.tokens.head.index).distinct
+            (
+                nums ++
+                ns.filter(t => !usedToks.contains(t)).flatMap(mkSolidNumUnit) 
++
+                findFuzzy(ns)
+            ).sortBy(_.tokens.head.index).distinct
         }
     }
 }
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/nlp/enrichers/numeric/NCNumericEnricher.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/nlp/enrichers/numeric/NCNumericEnricher.scala
index e2cfb5c..b28f198 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/server/nlp/enrichers/numeric/NCNumericEnricher.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/server/nlp/enrichers/numeric/NCNumericEnricher.scala
@@ -438,7 +438,6 @@ object NCNumericEnricher extends NCServerEnricher {
     
                 num.tokens.foreach(_.add(note))
             }
-    
         }
     }
 }
\ No newline at end of file
diff --git a/nlpcraft/src/test/scala/org/apache/nlpcraft/NCTestContext.scala 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/NCTestContext.scala
index 0201315..d0b3ceb 100644
--- a/nlpcraft/src/test/scala/org/apache/nlpcraft/NCTestContext.scala
+++ b/nlpcraft/src/test/scala/org/apache/nlpcraft/NCTestContext.scala
@@ -165,6 +165,21 @@ abstract class NCTestContext {
         assertEquals(expResp, resExtractor(res.getResult.get))
     }
 
+    /**
+      *
+      * @param req
+      * @param resExtractor
+      * @param validator
+      * @tparam T
+      */
+    protected def checkResult[T](req: String, resExtractor: String => T, 
validator: T => Boolean): Unit = {
+        val res = getClient.ask(req)
+
+        assertTrue(res.isOk, s"Unexpected result, 
error=${res.getResultError.orElse(null)}")
+        assertTrue(res.getResult.isPresent)
+        assertTrue(validator.apply(resExtractor(res.getResult.get)))
+    }
+
     final protected def getClient: NCTestClient = {
         if (cli == null)
             throw new IllegalStateException("Client is not started.")

Reply via email to