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

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 0826b71294f1a8a7583780bebc33fa0200aecdd7
Author: Xinyuan Lin <[email protected]>
AuthorDate: Sun Jun 28 11:41:47 2026 -0700

    test(workflow-operator): add unit test coverage for Sklearn training NB & 
tree descriptors (#5954)
    
    ### What changes were proposed in this PR?
    
    Pin behavior of eight previously-untested Sklearn **training** operator
    descriptors in `common/workflow-operator`. No production-code changes.
    
    | Spec | Source class |
    | --- | --- |
    | `SklearnTrainingBernoulliNaiveBayesOpDescSpec` |
    `SklearnTrainingBernoulliNaiveBayesOpDesc` |
    | `SklearnTrainingComplementNaiveBayesOpDescSpec` |
    `SklearnTrainingComplementNaiveBayesOpDesc` |
    | `SklearnTrainingGaussianNaiveBayesOpDescSpec` |
    `SklearnTrainingGaussianNaiveBayesOpDesc` |
    | `SklearnTrainingMultinomialNaiveBayesOpDescSpec` |
    `SklearnTrainingMultinomialNaiveBayesOpDesc` |
    | `SklearnTrainingDecisionTreeOpDescSpec` |
    `SklearnTrainingDecisionTreeOpDesc` |
    | `SklearnTrainingExtraTreeOpDescSpec` |
    `SklearnTrainingExtraTreeOpDesc` |
    | `SklearnTrainingExtraTreesOpDescSpec` |
    `SklearnTrainingExtraTreesOpDesc` |
    | `SklearnTrainingRandomForestOpDescSpec` |
    `SklearnTrainingRandomForestOpDesc` |
    
    **Behavior pinned** (shared `SklearnTrainingOpDesc` contract)
    
    | Surface | Contract |
    | --- | --- |
    | `operatorInfo` | exact model name (`Training: <model>`) + `Sklearn
    <name> Operator` description; **Sklearn Training** group; single
    `training` input port; one blocking output |
    | field defaults | `countVectorizer`/`tfidfTransformer` `false`;
    `target`/`text` `null` |
    | `getOutputSchemas` | `model_name` (STRING) + `model` (BINARY) keyed by
    the declared output port |
    | `generatePythonCode` | imports the matching sklearn estimator and
    builds the `make_pipeline(...).fit(X, Y)` training model |
    | Round-trip | config fields preserved through the polymorphic
    `LogicalOp` base, with the correct `operatorType` discriminator |
    
    ### Any related issues, documentation, discussions?
    
    Part of the ongoing `workflow-operator` unit-test coverage effort (the
    training-side counterpart to the Sklearn classifier coverage in
    #5925/#5939/#5940/#5941/#5945/#5946/#5951).
    
    ### How was this PR tested?
    
    - `sbt "WorkflowOperator/testOnly
    org.apache.texera.amber.operator.sklearn.training.*"` — 40 tests, all
    green
    - `sbt "WorkflowOperator/Test/scalafmtCheck"` and `sbt
    "WorkflowOperator/scalafixAll --check"` — clean
    - CI to confirm
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 ...earnTrainingBernoulliNaiveBayesOpDescSpec.scala | 79 ++++++++++++++++++++++
 ...arnTrainingComplementNaiveBayesOpDescSpec.scala | 79 ++++++++++++++++++++++
 .../SklearnTrainingDecisionTreeOpDescSpec.scala    | 79 ++++++++++++++++++++++
 .../SklearnTrainingExtraTreeOpDescSpec.scala       | 79 ++++++++++++++++++++++
 .../SklearnTrainingExtraTreesOpDescSpec.scala      | 79 ++++++++++++++++++++++
 ...learnTrainingGaussianNaiveBayesOpDescSpec.scala | 79 ++++++++++++++++++++++
 ...rnTrainingMultinomialNaiveBayesOpDescSpec.scala | 79 ++++++++++++++++++++++
 .../SklearnTrainingRandomForestOpDescSpec.scala    | 79 ++++++++++++++++++++++
 8 files changed, 632 insertions(+)

diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingBernoulliNaiveBayesOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingBernoulliNaiveBayesOpDescSpec.scala
new file mode 100644
index 0000000000..7bfe9c0905
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingBernoulliNaiveBayesOpDescSpec.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.texera.amber.operator.sklearn.training
+
+import org.apache.texera.amber.core.tuple.AttributeType
+import org.apache.texera.amber.operator.LogicalOp
+import org.apache.texera.amber.operator.metadata.OperatorGroupConstants
+import org.apache.texera.amber.util.JSONUtils.objectMapper
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class SklearnTrainingBernoulliNaiveBayesOpDescSpec extends AnyFlatSpec with 
Matchers {
+
+  "SklearnTrainingBernoulliNaiveBayesOpDesc.operatorInfo" should
+    "advertise the model name, Sklearn Training group, and the single training 
port" in {
+    val info = (new SklearnTrainingBernoulliNaiveBayesOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Training: Bernoulli Naive Bayes"
+    info.operatorDescription shouldBe "Sklearn Training: Bernoulli Naive Bayes 
Operator"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.SKLEARN_TRAINING_GROUP
+    info.inputPorts.map(_.displayName) shouldBe List("training")
+    info.outputPorts should have length 1
+    info.outputPorts.head.blocking shouldBe true
+  }
+
+  "SklearnTrainingBernoulliNaiveBayesOpDesc" should "default its config 
fields" in {
+    val d = new SklearnTrainingBernoulliNaiveBayesOpDesc
+    d.countVectorizer shouldBe false
+    d.tfidfTransformer shouldBe false
+    d.target shouldBe null
+    d.text shouldBe null
+  }
+
+  "SklearnTrainingBernoulliNaiveBayesOpDesc.getOutputSchemas" should
+    "emit the model_name/model schema keyed by the declared output port" in {
+    val d = new SklearnTrainingBernoulliNaiveBayesOpDesc
+    val schema = 
d.getOutputSchemas(Map.empty)(d.operatorInfo.outputPorts.head.id)
+    schema.getAttribute("model_name").getType shouldBe AttributeType.STRING
+    schema.getAttribute("model").getType shouldBe AttributeType.BINARY
+  }
+
+  "SklearnTrainingBernoulliNaiveBayesOpDesc.generatePythonCode" should "import 
the configured sklearn estimator" in {
+    val d = new SklearnTrainingBernoulliNaiveBayesOpDesc
+    d.target = "y"
+    val code = d.generatePythonCode()
+    code should include("from sklearn.naive_bayes import BernoulliNB")
+    code should include("make_pipeline")
+    code should include("Training: Bernoulli Naive Bayes")
+  }
+
+  "SklearnTrainingBernoulliNaiveBayesOpDesc" should "round-trip its config 
fields through the polymorphic base" in {
+    val d = new SklearnTrainingBernoulliNaiveBayesOpDesc
+    d.target = "label"
+    d.countVectorizer = true
+    val json = objectMapper.writeValueAsString(d)
+    json should 
include("\"operatorType\":\"SklearnTrainingBernoulliNaiveBayes\"")
+    val restored = objectMapper.readValue(json, classOf[LogicalOp])
+    restored shouldBe a[SklearnTrainingBernoulliNaiveBayesOpDesc]
+    val r = restored.asInstanceOf[SklearnTrainingBernoulliNaiveBayesOpDesc]
+    r.target shouldBe "label"
+    r.countVectorizer shouldBe true
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingComplementNaiveBayesOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingComplementNaiveBayesOpDescSpec.scala
new file mode 100644
index 0000000000..1c30b439b2
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingComplementNaiveBayesOpDescSpec.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.texera.amber.operator.sklearn.training
+
+import org.apache.texera.amber.core.tuple.AttributeType
+import org.apache.texera.amber.operator.LogicalOp
+import org.apache.texera.amber.operator.metadata.OperatorGroupConstants
+import org.apache.texera.amber.util.JSONUtils.objectMapper
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class SklearnTrainingComplementNaiveBayesOpDescSpec extends AnyFlatSpec with 
Matchers {
+
+  "SklearnTrainingComplementNaiveBayesOpDesc.operatorInfo" should
+    "advertise the model name, Sklearn Training group, and the single training 
port" in {
+    val info = (new SklearnTrainingComplementNaiveBayesOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Training: Complement Naive Bayes"
+    info.operatorDescription shouldBe "Sklearn Training: Complement Naive 
Bayes Operator"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.SKLEARN_TRAINING_GROUP
+    info.inputPorts.map(_.displayName) shouldBe List("training")
+    info.outputPorts should have length 1
+    info.outputPorts.head.blocking shouldBe true
+  }
+
+  "SklearnTrainingComplementNaiveBayesOpDesc" should "default its config 
fields" in {
+    val d = new SklearnTrainingComplementNaiveBayesOpDesc
+    d.countVectorizer shouldBe false
+    d.tfidfTransformer shouldBe false
+    d.target shouldBe null
+    d.text shouldBe null
+  }
+
+  "SklearnTrainingComplementNaiveBayesOpDesc.getOutputSchemas" should
+    "emit the model_name/model schema keyed by the declared output port" in {
+    val d = new SklearnTrainingComplementNaiveBayesOpDesc
+    val schema = 
d.getOutputSchemas(Map.empty)(d.operatorInfo.outputPorts.head.id)
+    schema.getAttribute("model_name").getType shouldBe AttributeType.STRING
+    schema.getAttribute("model").getType shouldBe AttributeType.BINARY
+  }
+
+  "SklearnTrainingComplementNaiveBayesOpDesc.generatePythonCode" should 
"import the configured sklearn estimator" in {
+    val d = new SklearnTrainingComplementNaiveBayesOpDesc
+    d.target = "y"
+    val code = d.generatePythonCode()
+    code should include("from sklearn.naive_bayes import ComplementNB")
+    code should include("make_pipeline")
+    code should include("Training: Complement Naive Bayes")
+  }
+
+  "SklearnTrainingComplementNaiveBayesOpDesc" should "round-trip its config 
fields through the polymorphic base" in {
+    val d = new SklearnTrainingComplementNaiveBayesOpDesc
+    d.target = "label"
+    d.countVectorizer = true
+    val json = objectMapper.writeValueAsString(d)
+    json should 
include("\"operatorType\":\"SklearnTrainingComplementNaiveBayes\"")
+    val restored = objectMapper.readValue(json, classOf[LogicalOp])
+    restored shouldBe a[SklearnTrainingComplementNaiveBayesOpDesc]
+    val r = restored.asInstanceOf[SklearnTrainingComplementNaiveBayesOpDesc]
+    r.target shouldBe "label"
+    r.countVectorizer shouldBe true
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingDecisionTreeOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingDecisionTreeOpDescSpec.scala
new file mode 100644
index 0000000000..33031a81d7
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingDecisionTreeOpDescSpec.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.texera.amber.operator.sklearn.training
+
+import org.apache.texera.amber.core.tuple.AttributeType
+import org.apache.texera.amber.operator.LogicalOp
+import org.apache.texera.amber.operator.metadata.OperatorGroupConstants
+import org.apache.texera.amber.util.JSONUtils.objectMapper
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class SklearnTrainingDecisionTreeOpDescSpec extends AnyFlatSpec with Matchers {
+
+  "SklearnTrainingDecisionTreeOpDesc.operatorInfo" should
+    "advertise the model name, Sklearn Training group, and the single training 
port" in {
+    val info = (new SklearnTrainingDecisionTreeOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Training: Decision Tree"
+    info.operatorDescription shouldBe "Sklearn Training: Decision Tree 
Operator"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.SKLEARN_TRAINING_GROUP
+    info.inputPorts.map(_.displayName) shouldBe List("training")
+    info.outputPorts should have length 1
+    info.outputPorts.head.blocking shouldBe true
+  }
+
+  "SklearnTrainingDecisionTreeOpDesc" should "default its config fields" in {
+    val d = new SklearnTrainingDecisionTreeOpDesc
+    d.countVectorizer shouldBe false
+    d.tfidfTransformer shouldBe false
+    d.target shouldBe null
+    d.text shouldBe null
+  }
+
+  "SklearnTrainingDecisionTreeOpDesc.getOutputSchemas" should
+    "emit the model_name/model schema keyed by the declared output port" in {
+    val d = new SklearnTrainingDecisionTreeOpDesc
+    val schema = 
d.getOutputSchemas(Map.empty)(d.operatorInfo.outputPorts.head.id)
+    schema.getAttribute("model_name").getType shouldBe AttributeType.STRING
+    schema.getAttribute("model").getType shouldBe AttributeType.BINARY
+  }
+
+  "SklearnTrainingDecisionTreeOpDesc.generatePythonCode" should "import the 
configured sklearn estimator" in {
+    val d = new SklearnTrainingDecisionTreeOpDesc
+    d.target = "y"
+    val code = d.generatePythonCode()
+    code should include("from sklearn.tree import DecisionTreeClassifier")
+    code should include("make_pipeline")
+    code should include("Training: Decision Tree")
+  }
+
+  "SklearnTrainingDecisionTreeOpDesc" should "round-trip its config fields 
through the polymorphic base" in {
+    val d = new SklearnTrainingDecisionTreeOpDesc
+    d.target = "label"
+    d.countVectorizer = true
+    val json = objectMapper.writeValueAsString(d)
+    json should include("\"operatorType\":\"SklearnTrainingDecisionTree\"")
+    val restored = objectMapper.readValue(json, classOf[LogicalOp])
+    restored shouldBe a[SklearnTrainingDecisionTreeOpDesc]
+    val r = restored.asInstanceOf[SklearnTrainingDecisionTreeOpDesc]
+    r.target shouldBe "label"
+    r.countVectorizer shouldBe true
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingExtraTreeOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingExtraTreeOpDescSpec.scala
new file mode 100644
index 0000000000..f43606ed5a
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingExtraTreeOpDescSpec.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.texera.amber.operator.sklearn.training
+
+import org.apache.texera.amber.core.tuple.AttributeType
+import org.apache.texera.amber.operator.LogicalOp
+import org.apache.texera.amber.operator.metadata.OperatorGroupConstants
+import org.apache.texera.amber.util.JSONUtils.objectMapper
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class SklearnTrainingExtraTreeOpDescSpec extends AnyFlatSpec with Matchers {
+
+  "SklearnTrainingExtraTreeOpDesc.operatorInfo" should
+    "advertise the model name, Sklearn Training group, and the single training 
port" in {
+    val info = (new SklearnTrainingExtraTreeOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Training: Extra Tree"
+    info.operatorDescription shouldBe "Sklearn Training: Extra Tree Operator"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.SKLEARN_TRAINING_GROUP
+    info.inputPorts.map(_.displayName) shouldBe List("training")
+    info.outputPorts should have length 1
+    info.outputPorts.head.blocking shouldBe true
+  }
+
+  "SklearnTrainingExtraTreeOpDesc" should "default its config fields" in {
+    val d = new SklearnTrainingExtraTreeOpDesc
+    d.countVectorizer shouldBe false
+    d.tfidfTransformer shouldBe false
+    d.target shouldBe null
+    d.text shouldBe null
+  }
+
+  "SklearnTrainingExtraTreeOpDesc.getOutputSchemas" should
+    "emit the model_name/model schema keyed by the declared output port" in {
+    val d = new SklearnTrainingExtraTreeOpDesc
+    val schema = 
d.getOutputSchemas(Map.empty)(d.operatorInfo.outputPorts.head.id)
+    schema.getAttribute("model_name").getType shouldBe AttributeType.STRING
+    schema.getAttribute("model").getType shouldBe AttributeType.BINARY
+  }
+
+  "SklearnTrainingExtraTreeOpDesc.generatePythonCode" should "import the 
configured sklearn estimator" in {
+    val d = new SklearnTrainingExtraTreeOpDesc
+    d.target = "y"
+    val code = d.generatePythonCode()
+    code should include("from sklearn.tree import ExtraTreeClassifier")
+    code should include("make_pipeline")
+    code should include("Training: Extra Tree")
+  }
+
+  "SklearnTrainingExtraTreeOpDesc" should "round-trip its config fields 
through the polymorphic base" in {
+    val d = new SklearnTrainingExtraTreeOpDesc
+    d.target = "label"
+    d.countVectorizer = true
+    val json = objectMapper.writeValueAsString(d)
+    json should include("\"operatorType\":\"SklearnTrainingExtraTree\"")
+    val restored = objectMapper.readValue(json, classOf[LogicalOp])
+    restored shouldBe a[SklearnTrainingExtraTreeOpDesc]
+    val r = restored.asInstanceOf[SklearnTrainingExtraTreeOpDesc]
+    r.target shouldBe "label"
+    r.countVectorizer shouldBe true
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingExtraTreesOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingExtraTreesOpDescSpec.scala
new file mode 100644
index 0000000000..e404a05c0d
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingExtraTreesOpDescSpec.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.texera.amber.operator.sklearn.training
+
+import org.apache.texera.amber.core.tuple.AttributeType
+import org.apache.texera.amber.operator.LogicalOp
+import org.apache.texera.amber.operator.metadata.OperatorGroupConstants
+import org.apache.texera.amber.util.JSONUtils.objectMapper
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class SklearnTrainingExtraTreesOpDescSpec extends AnyFlatSpec with Matchers {
+
+  "SklearnTrainingExtraTreesOpDesc.operatorInfo" should
+    "advertise the model name, Sklearn Training group, and the single training 
port" in {
+    val info = (new SklearnTrainingExtraTreesOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Training: Extra Trees"
+    info.operatorDescription shouldBe "Sklearn Training: Extra Trees Operator"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.SKLEARN_TRAINING_GROUP
+    info.inputPorts.map(_.displayName) shouldBe List("training")
+    info.outputPorts should have length 1
+    info.outputPorts.head.blocking shouldBe true
+  }
+
+  "SklearnTrainingExtraTreesOpDesc" should "default its config fields" in {
+    val d = new SklearnTrainingExtraTreesOpDesc
+    d.countVectorizer shouldBe false
+    d.tfidfTransformer shouldBe false
+    d.target shouldBe null
+    d.text shouldBe null
+  }
+
+  "SklearnTrainingExtraTreesOpDesc.getOutputSchemas" should
+    "emit the model_name/model schema keyed by the declared output port" in {
+    val d = new SklearnTrainingExtraTreesOpDesc
+    val schema = 
d.getOutputSchemas(Map.empty)(d.operatorInfo.outputPorts.head.id)
+    schema.getAttribute("model_name").getType shouldBe AttributeType.STRING
+    schema.getAttribute("model").getType shouldBe AttributeType.BINARY
+  }
+
+  "SklearnTrainingExtraTreesOpDesc.generatePythonCode" should "import the 
configured sklearn estimator" in {
+    val d = new SklearnTrainingExtraTreesOpDesc
+    d.target = "y"
+    val code = d.generatePythonCode()
+    code should include("from sklearn.ensemble import ExtraTreesClassifier")
+    code should include("make_pipeline")
+    code should include("Training: Extra Trees")
+  }
+
+  "SklearnTrainingExtraTreesOpDesc" should "round-trip its config fields 
through the polymorphic base" in {
+    val d = new SklearnTrainingExtraTreesOpDesc
+    d.target = "label"
+    d.countVectorizer = true
+    val json = objectMapper.writeValueAsString(d)
+    json should include("\"operatorType\":\"SklearnTrainingExtraTrees\"")
+    val restored = objectMapper.readValue(json, classOf[LogicalOp])
+    restored shouldBe a[SklearnTrainingExtraTreesOpDesc]
+    val r = restored.asInstanceOf[SklearnTrainingExtraTreesOpDesc]
+    r.target shouldBe "label"
+    r.countVectorizer shouldBe true
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingGaussianNaiveBayesOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingGaussianNaiveBayesOpDescSpec.scala
new file mode 100644
index 0000000000..7d193883cc
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingGaussianNaiveBayesOpDescSpec.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.texera.amber.operator.sklearn.training
+
+import org.apache.texera.amber.core.tuple.AttributeType
+import org.apache.texera.amber.operator.LogicalOp
+import org.apache.texera.amber.operator.metadata.OperatorGroupConstants
+import org.apache.texera.amber.util.JSONUtils.objectMapper
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class SklearnTrainingGaussianNaiveBayesOpDescSpec extends AnyFlatSpec with 
Matchers {
+
+  "SklearnTrainingGaussianNaiveBayesOpDesc.operatorInfo" should
+    "advertise the model name, Sklearn Training group, and the single training 
port" in {
+    val info = (new SklearnTrainingGaussianNaiveBayesOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Training: Gaussian Naive Bayes"
+    info.operatorDescription shouldBe "Sklearn Training: Gaussian Naive Bayes 
Operator"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.SKLEARN_TRAINING_GROUP
+    info.inputPorts.map(_.displayName) shouldBe List("training")
+    info.outputPorts should have length 1
+    info.outputPorts.head.blocking shouldBe true
+  }
+
+  "SklearnTrainingGaussianNaiveBayesOpDesc" should "default its config fields" 
in {
+    val d = new SklearnTrainingGaussianNaiveBayesOpDesc
+    d.countVectorizer shouldBe false
+    d.tfidfTransformer shouldBe false
+    d.target shouldBe null
+    d.text shouldBe null
+  }
+
+  "SklearnTrainingGaussianNaiveBayesOpDesc.getOutputSchemas" should
+    "emit the model_name/model schema keyed by the declared output port" in {
+    val d = new SklearnTrainingGaussianNaiveBayesOpDesc
+    val schema = 
d.getOutputSchemas(Map.empty)(d.operatorInfo.outputPorts.head.id)
+    schema.getAttribute("model_name").getType shouldBe AttributeType.STRING
+    schema.getAttribute("model").getType shouldBe AttributeType.BINARY
+  }
+
+  "SklearnTrainingGaussianNaiveBayesOpDesc.generatePythonCode" should "import 
the configured sklearn estimator" in {
+    val d = new SklearnTrainingGaussianNaiveBayesOpDesc
+    d.target = "y"
+    val code = d.generatePythonCode()
+    code should include("from sklearn.naive_bayes import GaussianNB")
+    code should include("make_pipeline")
+    code should include("Training: Gaussian Naive Bayes")
+  }
+
+  "SklearnTrainingGaussianNaiveBayesOpDesc" should "round-trip its config 
fields through the polymorphic base" in {
+    val d = new SklearnTrainingGaussianNaiveBayesOpDesc
+    d.target = "label"
+    d.countVectorizer = true
+    val json = objectMapper.writeValueAsString(d)
+    json should 
include("\"operatorType\":\"SklearnTrainingGaussianNaiveBayes\"")
+    val restored = objectMapper.readValue(json, classOf[LogicalOp])
+    restored shouldBe a[SklearnTrainingGaussianNaiveBayesOpDesc]
+    val r = restored.asInstanceOf[SklearnTrainingGaussianNaiveBayesOpDesc]
+    r.target shouldBe "label"
+    r.countVectorizer shouldBe true
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingMultinomialNaiveBayesOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingMultinomialNaiveBayesOpDescSpec.scala
new file mode 100644
index 0000000000..6e1bda2cf1
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingMultinomialNaiveBayesOpDescSpec.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.texera.amber.operator.sklearn.training
+
+import org.apache.texera.amber.core.tuple.AttributeType
+import org.apache.texera.amber.operator.LogicalOp
+import org.apache.texera.amber.operator.metadata.OperatorGroupConstants
+import org.apache.texera.amber.util.JSONUtils.objectMapper
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class SklearnTrainingMultinomialNaiveBayesOpDescSpec extends AnyFlatSpec with 
Matchers {
+
+  "SklearnTrainingMultinomialNaiveBayesOpDesc.operatorInfo" should
+    "advertise the model name, Sklearn Training group, and the single training 
port" in {
+    val info = (new SklearnTrainingMultinomialNaiveBayesOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Training: Multinomial Naive Bayes"
+    info.operatorDescription shouldBe "Sklearn Training: Multinomial Naive 
Bayes Operator"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.SKLEARN_TRAINING_GROUP
+    info.inputPorts.map(_.displayName) shouldBe List("training")
+    info.outputPorts should have length 1
+    info.outputPorts.head.blocking shouldBe true
+  }
+
+  "SklearnTrainingMultinomialNaiveBayesOpDesc" should "default its config 
fields" in {
+    val d = new SklearnTrainingMultinomialNaiveBayesOpDesc
+    d.countVectorizer shouldBe false
+    d.tfidfTransformer shouldBe false
+    d.target shouldBe null
+    d.text shouldBe null
+  }
+
+  "SklearnTrainingMultinomialNaiveBayesOpDesc.getOutputSchemas" should
+    "emit the model_name/model schema keyed by the declared output port" in {
+    val d = new SklearnTrainingMultinomialNaiveBayesOpDesc
+    val schema = 
d.getOutputSchemas(Map.empty)(d.operatorInfo.outputPorts.head.id)
+    schema.getAttribute("model_name").getType shouldBe AttributeType.STRING
+    schema.getAttribute("model").getType shouldBe AttributeType.BINARY
+  }
+
+  "SklearnTrainingMultinomialNaiveBayesOpDesc.generatePythonCode" should 
"import the configured sklearn estimator" in {
+    val d = new SklearnTrainingMultinomialNaiveBayesOpDesc
+    d.target = "y"
+    val code = d.generatePythonCode()
+    code should include("from sklearn.naive_bayes import MultinomialNB")
+    code should include("make_pipeline")
+    code should include("Training: Multinomial Naive Bayes")
+  }
+
+  "SklearnTrainingMultinomialNaiveBayesOpDesc" should "round-trip its config 
fields through the polymorphic base" in {
+    val d = new SklearnTrainingMultinomialNaiveBayesOpDesc
+    d.target = "label"
+    d.countVectorizer = true
+    val json = objectMapper.writeValueAsString(d)
+    json should 
include("\"operatorType\":\"SklearnTrainingMultinomialNaiveBayes\"")
+    val restored = objectMapper.readValue(json, classOf[LogicalOp])
+    restored shouldBe a[SklearnTrainingMultinomialNaiveBayesOpDesc]
+    val r = restored.asInstanceOf[SklearnTrainingMultinomialNaiveBayesOpDesc]
+    r.target shouldBe "label"
+    r.countVectorizer shouldBe true
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingRandomForestOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingRandomForestOpDescSpec.scala
new file mode 100644
index 0000000000..045bbd2b5a
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/sklearn/training/SklearnTrainingRandomForestOpDescSpec.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.texera.amber.operator.sklearn.training
+
+import org.apache.texera.amber.core.tuple.AttributeType
+import org.apache.texera.amber.operator.LogicalOp
+import org.apache.texera.amber.operator.metadata.OperatorGroupConstants
+import org.apache.texera.amber.util.JSONUtils.objectMapper
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class SklearnTrainingRandomForestOpDescSpec extends AnyFlatSpec with Matchers {
+
+  "SklearnTrainingRandomForestOpDesc.operatorInfo" should
+    "advertise the model name, Sklearn Training group, and the single training 
port" in {
+    val info = (new SklearnTrainingRandomForestOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Training: Random Forest"
+    info.operatorDescription shouldBe "Sklearn Training: Random Forest 
Operator"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.SKLEARN_TRAINING_GROUP
+    info.inputPorts.map(_.displayName) shouldBe List("training")
+    info.outputPorts should have length 1
+    info.outputPorts.head.blocking shouldBe true
+  }
+
+  "SklearnTrainingRandomForestOpDesc" should "default its config fields" in {
+    val d = new SklearnTrainingRandomForestOpDesc
+    d.countVectorizer shouldBe false
+    d.tfidfTransformer shouldBe false
+    d.target shouldBe null
+    d.text shouldBe null
+  }
+
+  "SklearnTrainingRandomForestOpDesc.getOutputSchemas" should
+    "emit the model_name/model schema keyed by the declared output port" in {
+    val d = new SklearnTrainingRandomForestOpDesc
+    val schema = 
d.getOutputSchemas(Map.empty)(d.operatorInfo.outputPorts.head.id)
+    schema.getAttribute("model_name").getType shouldBe AttributeType.STRING
+    schema.getAttribute("model").getType shouldBe AttributeType.BINARY
+  }
+
+  "SklearnTrainingRandomForestOpDesc.generatePythonCode" should "import the 
configured sklearn estimator" in {
+    val d = new SklearnTrainingRandomForestOpDesc
+    d.target = "y"
+    val code = d.generatePythonCode()
+    code should include("from sklearn.ensemble import RandomForestClassifier")
+    code should include("make_pipeline")
+    code should include("Training: Random Forest")
+  }
+
+  "SklearnTrainingRandomForestOpDesc" should "round-trip its config fields 
through the polymorphic base" in {
+    val d = new SklearnTrainingRandomForestOpDesc
+    d.target = "label"
+    d.countVectorizer = true
+    val json = objectMapper.writeValueAsString(d)
+    json should include("\"operatorType\":\"SklearnTrainingRandomForest\"")
+    val restored = objectMapper.readValue(json, classOf[LogicalOp])
+    restored shouldBe a[SklearnTrainingRandomForestOpDesc]
+    val r = restored.asInstanceOf[SklearnTrainingRandomForestOpDesc]
+    r.target shouldBe "label"
+    r.countVectorizer shouldBe true
+  }
+}


Reply via email to