You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nlpcraft.apache.org by se...@apache.org on 2020/03/15 11:29:24 UTC

[incubator-nlpcraft] branch NLPCRAFT-16 created (now 74ee4eb)

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

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


      at 74ee4eb  Tests infrastructure. WIP.

This branch includes the following new commits:

     new 7844b27  Tests infrastructure. Initial commit.
     new 74ee4eb  Tests infrastructure. WIP.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[incubator-nlpcraft] 02/02: Tests infrastructure. WIP.

Posted by se...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 74ee4ebd7909716297eb162667700a9db46a3b1d
Author: Sergey Kamov <se...@apache.org>
AuthorDate: Sun Mar 15 14:27:26 2020 +0300

    Tests infrastructure. WIP.
---
 .../mgrs/nlp/enrichers/NCEnricherBaseSpec.scala    |  38 ++++-
 .../mgrs/nlp/enrichers/NCEnricherTestModel.scala   |   3 +-
 .../mgrs/nlp/enrichers/NCEnrichersTestBeans.scala  | 159 +++++++++++++++------
 .../nlp/enrichers/sort/NCEnricherSortSpec.scala    |  12 +-
 4 files changed, 154 insertions(+), 58 deletions(-)

diff --git a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherBaseSpec.scala b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherBaseSpec.scala
index 07ef885..6c9e611 100644
--- a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherBaseSpec.scala
+++ b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherBaseSpec.scala
@@ -22,9 +22,11 @@ import org.apache.nlpcraft.probe.embedded.NCEmbeddedProbe
 import org.junit.jupiter.api.Assertions.{assertEquals, assertTrue}
 import org.junit.jupiter.api.{AfterEach, BeforeEach}
 
-
+/**
+  * Enrichers tests utility base class.
+  */
 class NCEnricherBaseSpec {
-    private var client: NCTestClient = null
+    private var client: NCTestClient = _
 
     @BeforeEach
     private[enrichers] def setUp(): Unit = {
@@ -36,11 +38,10 @@ class NCEnricherBaseSpec {
     }
 
     @AfterEach
-    private[enrichers] def tearDown(): Unit = {
-        client.close()
-    }
+    private[enrichers] def tearDown(): Unit = client.close()
 
     /**
+      * Checks single variant.
       *
       * @param reqTxt
       * @param expToks
@@ -51,6 +52,31 @@ class NCEnricherBaseSpec {
         assertTrue(res.isOk)
         assertTrue(res.getResult.isPresent)
 
-        assertEquals(NCTestSentence(expToks), NCTestSentence.deserialize(res.getResult.get()))
+        val sens = NCTestSentence.deserialize(res.getResult.get())
+
+        require(sens.size == 1)
+
+        assertEquals(NCTestSentence(expToks), sens.head)
+    }
+
+    /**
+      * Checks multiple variants.
+      *
+      * @param reqTxt
+      * @param expToks
+      */
+    private[enrichers] def checkMultiple(reqTxt: String, expToks: Seq[Seq[NCTestToken]]): Unit = {
+        val res = client.ask(reqTxt)
+
+        assertTrue(res.isOk)
+        assertTrue(res.getResult.isPresent)
+
+        val sens1 = expToks.map(e ⇒ NCTestSentence(e))
+        val sens2 = NCTestSentence.deserialize(res.getResult.get())
+
+        require(sens1.size == sens2.size)
+
+        for (sen ← sens1)
+            require(sens2.exists(_ == sen))
     }
 }
diff --git a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherTestModel.scala b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherTestModel.scala
index dbfc435..7fa6112 100644
--- a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherTestModel.scala
+++ b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherTestModel.scala
@@ -52,10 +52,9 @@ class NCEnricherTestModel extends NCModelAdapter(ID, "Model enrichers test", "1.
             override def getGroups: util.List[String] = Collections.singletonList(GROUP)
         }
 
-    // TODO: multiple variant.
     override def onContext(ctx: NCContext): NCResult =
         NCResult.text(
-            NCTestSentence.serialize(NCTestSentence(ctx.getVariants.asScala.head.asScala.map(t ⇒ NCTestToken(t))))
+            NCTestSentence.serialize(ctx.getVariants.asScala.map(v ⇒ NCTestSentence(v.asScala.map(t ⇒ NCTestToken(t)))))
         )
 }
 
diff --git a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala
index b8ee248..5fa79ae 100644
--- a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala
+++ b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala
@@ -17,50 +17,109 @@
 
 package org.apache.nlpcraft.probe.mgrs.nlp.enrichers
 
-import java.util
+import java.io.{ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream}
+import java.nio.charset.StandardCharsets.UTF_8
+import java.util.Base64
 
-import com.google.gson.Gson
-import com.google.gson.reflect.TypeToken
 import org.apache.nlpcraft.model.NCToken
-import scala.collection.JavaConverters._
-import scala.language.implicitConversions
+import resource.managed
 
-sealed trait NCTestNote {
+/**
+  * Tests infrastructure beans.
+  */
+
+sealed trait NCTestTokenKind {
     def id: String
 }
 
-case class NCTestSort(
+// Simplified set of tokens data. Added only fields for validation.
+
+// Server enrichers.
+case class NCTestNlpToken() extends NCTestTokenKind {
+    override def id: String = "nlpcraft:nlp"
+}
+
+// Skip non-deteministric properties verification.
+case class NCTestDateToken() extends NCTestTokenKind {
+    override def id: String = "nlpcraft:date"
+}
+
+case class NCTestCoordinateToken(latitude: Double, longitude: Double) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:coordinate"
+}
+
+case class NCTestNumericToken(from: Double, to: Double) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:num"
+}
+
+case class NCTestCityToken(city: String) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:city"
+}
+
+case class NCTestCountryToken(country: String) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:country"
+}
+
+case class NCTestRegionToken(region: String) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:region"
+}
+
+case class NCTestContinentToken(continent: String) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:continent"
+}
+
+case class NCTestSubcontinentToken(city: String) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:subcontinent"
+}
+
+case class NCTestMetroToken(metro: String) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:metro"
+}
+
+// Probe enrichers.
+case class NCTestSortToken(
     asc: Option[Boolean],
-    subjNotes: java.util.List[String],
-    subjIndexes: java.util.List[Int],
-    byNotes: Option[java.util.List[String]],
-    byIndexes: Option[java.util.List[Int]]
-) extends NCTestNote {
+    subjNotes: Seq[String],
+    subjIndexes: Seq[Int],
+    byNotes: Option[Seq[String]],
+    byIndexes: Option[Seq[Int]]
+) extends NCTestTokenKind {
     override def id: String = "nlpcraft:sort"
 }
 
-case class NCTestRelation(`type`: String, indexes: java.util.List[Int], note: String) extends NCTestNote {
+case class NCTestRelationToken(`type`: String, indexes: Seq[Int], note: String) extends NCTestTokenKind {
     override def id: String = "nlpcraft:relation"
 }
 
-case class NCTestCommonNote(id: String) extends NCTestNote
+case class NCTestAggregationToken(`type`: String, indexes: Seq[Int], note: String) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:aggregation"
+}
+
+case class NCTestLimitToken(limit: Double, indexes: Seq[Int], note: String, asc: Option[Boolean]) extends NCTestTokenKind {
+    override def id: String = "nlpcraft:limit"
+}
 
-// TODO: add others
-case class NCTestToken(
-    origText: String,
-    isStop: Boolean,
-    note: NCTestNote
-)
+// Token and sentence.
+case class NCTestToken(origText: String, isStop: Boolean, note: NCTestTokenKind)
 
 object NCTestToken {
     def apply(t: NCToken): NCTestToken = {
-        val note: NCTestNote =
+        val note: NCTestTokenKind =
             t.getId match {
-                case "nlpcraft:nlp" | "nlpcraft:coordinate" | "nlpcraft:num" | "nlpcraft:date" ⇒
-                    NCTestCommonNote(t.getId)
+                case "nlpcraft:nlp" ⇒ NCTestNlpToken()
+                case "nlpcraft:coordinate" ⇒
+                    NCTestCoordinateToken(latitude = t.meta("latitude"), longitude = t.meta("longitude"))
+                case "nlpcraft:num" ⇒ NCTestNumericToken(from = t.meta("from"), to = t.meta("to"))
+                case "nlpcraft:date" ⇒ NCTestDateToken()
+                case "nlpcraft:city" ⇒ NCTestCityToken(city = t.meta("city"))
+                case "nlpcraft:region" ⇒ NCTestRegionToken(region = t.meta("region"))
+                case "nlpcraft:country" ⇒ NCTestCountryToken(country = t.meta("country"))
+                case "nlpcraft:subcontinent" ⇒ NCTestSubcontinentToken(city = t.meta("subcontinent"))
+                case "nlpcraft:continent" ⇒ NCTestContinentToken(continent = t.meta("continent"))
+                case "nlpcraft:metro" ⇒ NCTestMetroToken(metro = t.meta("metro"))
 
                 case "nlpcraft:sort" ⇒
-                    NCTestSort(
+                    NCTestSortToken(
                         asc = t.meta("asc"),
                         subjNotes = t.meta("subjnotes"),
                         subjIndexes = t.meta("subjindexes"),
@@ -68,11 +127,25 @@ object NCTestToken {
                         byIndexes = t.meta("byindexes")
                     )
                 case "nlpcraft:relation" ⇒
-                    NCTestRelation(
+                    NCTestRelationToken(
                         `type` = t.meta("type"),
                         indexes = t.meta("indexes"),
                         note = t.meta("note")
                     )
+                case "nlpcraft:aggregation" ⇒
+                    NCTestAggregationToken(
+                        `type` = t.meta("type"),
+                        indexes = t.meta("indexes"),
+                        note = t.meta("note")
+                    )
+
+                case "nlpcraft:limit" ⇒
+                    NCTestLimitToken(
+                        limit = t.meta("limit"),
+                        indexes = t.meta("indexes"),
+                        note = t.meta("note"),
+                        asc = t.meta("note")
+                    )
 
                 case _ ⇒ throw new AssertionError(s"Unsupported token: ${t.getId}")
             }
@@ -80,29 +153,27 @@ object NCTestToken {
         NCTestToken(t.getOriginalText, t.isStopWord, note)
     }
 
-    def apply(origText: String, isStop: Boolean): NCTestToken =
-        new NCTestToken(origText, isStop, NCTestCommonNote("nlpcraft:nlp"))
+    def apply(origText: String, isStop: Boolean): NCTestToken = new NCTestToken(origText, isStop, NCTestNlpToken())
 }
 
 case class NCTestSentence(tokens: Seq[NCTestToken])
 
 object NCTestSentence {
-    private val GSON = new Gson()
-    private val TYPE = new TypeToken[NCTestSentence]() {}.getType
-
-    def serialize(seq: NCTestSentence): String = {
-        GSON.toJson(seq)
-    }
-
-    def deserialize(s: String): NCTestSentence = {
-        GSON.fromJson(s, TYPE)
-    }
-}
-
-object a extends App {
-    val s = NCTestSentence.serialize(NCTestSentence(Seq(NCTestToken(origText = "text", isStop = false, note = NCTestRelation("a", Seq(1).asJava, "test")))))
+    def serialize(sens: Iterable[NCTestSentence]): String =
+        managed(new ByteArrayOutputStream()) acquireAndGet { bos ⇒
+            managed(new ObjectOutputStream(bos)) acquireAndGet { os ⇒
+                os.writeObject(sens)
 
-    println(s)
+                os.flush()
 
-    println(NCTestSentence.deserialize(s))
-}
+                new String(Base64.getEncoder.encode(bos.toByteArray), UTF_8)
+            }
+        }
+
+    def deserialize(s: String): Iterable[NCTestSentence] =
+        managed(new ObjectInputStream(
+            new ByteArrayInputStream(Base64.getDecoder.decode(s.getBytes(UTF_8))))
+        ) acquireAndGet { is ⇒
+            is.readObject.asInstanceOf[Iterable[NCTestSentence]]
+        }
+}
\ No newline at end of file
diff --git a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala
index 9d1ae95..1fb75a2 100644
--- a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala
+++ b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala
@@ -17,22 +17,22 @@
 
 package org.apache.nlpcraft.probe.mgrs.nlp.enrichers.sort
 
-import org.apache.nlpcraft.probe.mgrs.nlp.enrichers.{NCEnricherBaseSpec, NCTestRelation, NCTestToken}
+import org.apache.nlpcraft.probe.mgrs.nlp.enrichers.{NCEnricherBaseSpec, NCTestRelationToken, NCTestToken}
 import org.junit.jupiter.api.Test
 
-import scala.collection.JavaConverters._
-
+/**
+  * Sort enricher tests.
+  */
 class NCEnricherSortSpec extends NCEnricherBaseSpec {
-
     /**
       *
       * @throws Exception
       */
     @Test
-    private def test(): Unit = {
+    def test(): Unit = {
         check(
             "sort a1 by b1",
-            NCTestToken(origText = "text", isStop = false, note = NCTestRelation("a", Seq(1).asJava, "test"))
+            NCTestToken(origText = "text", isStop = false, note = NCTestRelationToken("a", Seq(1), "test"))
         )
     }
 }


[incubator-nlpcraft] 01/02: Tests infrastructure. Initial commit.

Posted by se...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7844b279927ced6a3b10c09758b516f935c40242
Author: Sergey Kamov <se...@apache.org>
AuthorDate: Sun Mar 15 13:05:46 2020 +0300

    Tests infrastructure. Initial commit.
---
 .../mgrs/nlp/enrichers/NCEnricherBaseSpec.scala    |  56 +++++++++++
 .../mgrs/nlp/enrichers/NCEnricherTestModel.scala   |  66 +++++++++++++
 .../mgrs/nlp/enrichers/NCEnrichersTestBeans.scala  | 108 +++++++++++++++++++++
 .../nlp/enrichers/sort/NCEnricherSortSpec.scala    |  38 ++++++++
 4 files changed, 268 insertions(+)

diff --git a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherBaseSpec.scala b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherBaseSpec.scala
new file mode 100644
index 0000000..07ef885
--- /dev/null
+++ b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherBaseSpec.scala
@@ -0,0 +1,56 @@
+/*
+ * 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.probe.mgrs.nlp.enrichers
+
+import org.apache.nlpcraft.model.tools.test.{NCTestClient, NCTestClientBuilder}
+import org.apache.nlpcraft.probe.embedded.NCEmbeddedProbe
+import org.junit.jupiter.api.Assertions.{assertEquals, assertTrue}
+import org.junit.jupiter.api.{AfterEach, BeforeEach}
+
+
+class NCEnricherBaseSpec {
+    private var client: NCTestClient = null
+
+    @BeforeEach
+    private[enrichers] def setUp(): Unit = {
+        NCEmbeddedProbe.start(classOf[NCEnricherTestModel])
+
+        client = new NCTestClientBuilder().newBuilder.build
+
+        client.open(NCEnricherTestModel.ID)
+    }
+
+    @AfterEach
+    private[enrichers] def tearDown(): Unit = {
+        client.close()
+    }
+
+    /**
+      *
+      * @param reqTxt
+      * @param expToks
+      */
+    private[enrichers] def check(reqTxt: String, expToks: NCTestToken*): Unit = {
+        val res = client.ask(reqTxt)
+
+        assertTrue(res.isOk)
+        assertTrue(res.getResult.isPresent)
+
+        assertEquals(NCTestSentence(expToks), NCTestSentence.deserialize(res.getResult.get()))
+    }
+}
diff --git a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherTestModel.scala b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherTestModel.scala
new file mode 100644
index 0000000..dbfc435
--- /dev/null
+++ b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnricherTestModel.scala
@@ -0,0 +1,66 @@
+/*
+ * 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.probe.mgrs.nlp.enrichers
+
+import java.util
+import java.util.Collections
+
+import org.apache.nlpcraft.model.{NCContext, NCElement, NCModelAdapter, NCResult}
+
+import scala.collection.JavaConverters._
+import scala.language.implicitConversions
+
+import NCEnricherTestModel._
+
+/**
+  * Enrichers test model.
+  */
+class NCEnricherTestModel extends NCModelAdapter(ID, "Model enrichers test", "1.0") {
+    private implicit def convert(s: String): NCResult = NCResult.text(s)
+
+    override def getElements: util.Set[NCElement] =
+        Set(
+            mkElement("A", Seq("A")),
+            mkElement("B", Seq("B")),
+            mkElement("C", Seq("C")),
+            mkElement("A B", Seq("A B")),
+            mkElement("B C", Seq("B C")),
+            mkElement("A B C", Seq("A B C")),
+            mkElement("D1", Seq("D")),
+            mkElement("D2", Seq("D"))
+        ).asJava
+
+    private def mkElement(id: String, syns: Seq[String]): NCElement =
+        new NCElement {
+            override def getId: String = id
+            override def getSynonyms: util.List[String] = syns.asJava
+            override def getGroups: util.List[String] = Collections.singletonList(GROUP)
+        }
+
+    // TODO: multiple variant.
+    override def onContext(ctx: NCContext): NCResult =
+        NCResult.text(
+            NCTestSentence.serialize(NCTestSentence(ctx.getVariants.asScala.head.asScala.map(t ⇒ NCTestToken(t))))
+        )
+}
+
+object NCEnricherTestModel {
+    final val ID = "test.enricher"
+
+    private final val GROUP = "test-enricher-group"
+}
diff --git a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala
new file mode 100644
index 0000000..b8ee248
--- /dev/null
+++ b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/NCEnrichersTestBeans.scala
@@ -0,0 +1,108 @@
+/*
+ * 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.probe.mgrs.nlp.enrichers
+
+import java.util
+
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import org.apache.nlpcraft.model.NCToken
+import scala.collection.JavaConverters._
+import scala.language.implicitConversions
+
+sealed trait NCTestNote {
+    def id: String
+}
+
+case class NCTestSort(
+    asc: Option[Boolean],
+    subjNotes: java.util.List[String],
+    subjIndexes: java.util.List[Int],
+    byNotes: Option[java.util.List[String]],
+    byIndexes: Option[java.util.List[Int]]
+) extends NCTestNote {
+    override def id: String = "nlpcraft:sort"
+}
+
+case class NCTestRelation(`type`: String, indexes: java.util.List[Int], note: String) extends NCTestNote {
+    override def id: String = "nlpcraft:relation"
+}
+
+case class NCTestCommonNote(id: String) extends NCTestNote
+
+// TODO: add others
+case class NCTestToken(
+    origText: String,
+    isStop: Boolean,
+    note: NCTestNote
+)
+
+object NCTestToken {
+    def apply(t: NCToken): NCTestToken = {
+        val note: NCTestNote =
+            t.getId match {
+                case "nlpcraft:nlp" | "nlpcraft:coordinate" | "nlpcraft:num" | "nlpcraft:date" ⇒
+                    NCTestCommonNote(t.getId)
+
+                case "nlpcraft:sort" ⇒
+                    NCTestSort(
+                        asc = t.meta("asc"),
+                        subjNotes = t.meta("subjnotes"),
+                        subjIndexes = t.meta("subjindexes"),
+                        byNotes = t.meta("bynotes"),
+                        byIndexes = t.meta("byindexes")
+                    )
+                case "nlpcraft:relation" ⇒
+                    NCTestRelation(
+                        `type` = t.meta("type"),
+                        indexes = t.meta("indexes"),
+                        note = t.meta("note")
+                    )
+
+                case _ ⇒ throw new AssertionError(s"Unsupported token: ${t.getId}")
+            }
+
+        NCTestToken(t.getOriginalText, t.isStopWord, note)
+    }
+
+    def apply(origText: String, isStop: Boolean): NCTestToken =
+        new NCTestToken(origText, isStop, NCTestCommonNote("nlpcraft:nlp"))
+}
+
+case class NCTestSentence(tokens: Seq[NCTestToken])
+
+object NCTestSentence {
+    private val GSON = new Gson()
+    private val TYPE = new TypeToken[NCTestSentence]() {}.getType
+
+    def serialize(seq: NCTestSentence): String = {
+        GSON.toJson(seq)
+    }
+
+    def deserialize(s: String): NCTestSentence = {
+        GSON.fromJson(s, TYPE)
+    }
+}
+
+object a extends App {
+    val s = NCTestSentence.serialize(NCTestSentence(Seq(NCTestToken(origText = "text", isStop = false, note = NCTestRelation("a", Seq(1).asJava, "test")))))
+
+    println(s)
+
+    println(NCTestSentence.deserialize(s))
+}
diff --git a/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala
new file mode 100644
index 0000000..9d1ae95
--- /dev/null
+++ b/src/test/scala/org/apache/nlpcraft/probe/mgrs/nlp/enrichers/sort/NCEnricherSortSpec.scala
@@ -0,0 +1,38 @@
+/*
+ * 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.probe.mgrs.nlp.enrichers.sort
+
+import org.apache.nlpcraft.probe.mgrs.nlp.enrichers.{NCEnricherBaseSpec, NCTestRelation, NCTestToken}
+import org.junit.jupiter.api.Test
+
+import scala.collection.JavaConverters._
+
+class NCEnricherSortSpec extends NCEnricherBaseSpec {
+
+    /**
+      *
+      * @throws Exception
+      */
+    @Test
+    private def test(): Unit = {
+        check(
+            "sort a1 by b1",
+            NCTestToken(origText = "text", isStop = false, note = NCTestRelation("a", Seq(1).asJava, "test"))
+        )
+    }
+}