Pytest for Feature Group CRUD Operations 11/15311/2 master
authorSwaraj Kumar/Open Network Innovation /SRI-Bangalore/Engineer/Samsung Electronics <swaraj.kumar@samsung.com>
Tue, 25 Nov 2025 05:56:15 +0000 (11:26 +0530)
committerSwaraj Kumar/Open Network Innovation /SRI-Bangalore/Engineer/Samsung Electronics <swaraj.kumar@samsung.com>
Tue, 25 Nov 2025 06:04:39 +0000 (11:34 +0530)
Add Automated Smoke Test for Feature Group CRUD Operations:
1.Create Feature Group
2.Duplicate Feature Group Creation
3.Get All Feature Groups
4.Update (Edit) Feature Group
5.Delete Feature Group

Issue-Id: AIMLFW-316
Change-Id: Ida8a843b9b8e1a2563a351a091807f0463e70d65
Signed-off-by: Swaraj Kumar/Open Network Innovation /SRI-Bangalore/Engineer/Samsung Electronics <swaraj.kumar@samsung.com>
.github/workflows/gerrit-verify.yaml
component-testing/tests/requirements.txt [new file with mode: 0644]
component-testing/tests/test_featuregroup.py [new file with mode: 0644]
tox.ini

index 9b301e2..4f79d4d 100644 (file)
@@ -199,6 +199,23 @@ jobs:
           kubectl get pods -n traininghost
           kubectl get svc -n traininghost
 
+      - name: run regression test
+        run: |
+          cd $GITHUB_WORKSPACE
+          pip install -r component-testing/tests/requirements.txt
+
+          kubectl port-forward svc/tm -n traininghost  32002:32002 & PF_PID=$!
+          sleep 1
+          python3 -m pytest component-testing/tests/ --maxfail=1 --disable-warnings -q --html=pytest_report.html
+
+          # Stop port-forwarding
+          kill $PF_PID
+      
+      - name: Upload Test Report
+        uses: actions/upload-artifact@v4
+        with:
+          name: pytest-report
+          path: pytest_report.html
   vote:
     if: ${{ always() }}
     # yamllint enable rule:line-length
diff --git a/component-testing/tests/requirements.txt b/component-testing/tests/requirements.txt
new file mode 100644 (file)
index 0000000..76a20ca
--- /dev/null
@@ -0,0 +1,3 @@
+requests
+pytest
+pytest-html
\ No newline at end of file
diff --git a/component-testing/tests/test_featuregroup.py b/component-testing/tests/test_featuregroup.py
new file mode 100644 (file)
index 0000000..97a8e2d
--- /dev/null
@@ -0,0 +1,144 @@
+# ==================================================================================
+#
+#       Copyright (c) 2025 Samsung Electronics Co., Ltd. All Rights Reserved.
+#
+#   Licensed 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.
+#
+# ==================================================================================
+import pytest
+import requests
+import time
+from http import HTTPStatus
+BASE_URL = "http://localhost:32002/ai-ml-model-training/v1/featureGroup"
+BASE_URL_SHORT = "http://localhost:32002/featureGroup"
+@pytest.mark.parametrize("fg_name", ["base2", "base3", "base4"])
+class TestFeatureGroupCRUD:
+    """End-to-end CRUD smoke tests for FeatureGroup API with cleanup"""
+    def setup_method(self, method):
+        """Ensure clean state before each run."""
+        self.cleanup_feature_group(method.__name__)
+        time.sleep(1)
+    def teardown_method(self, method):
+        """Clean up after each test, ensuring no leftovers."""
+        self.cleanup_feature_group(method.__name__)
+        time.sleep(1)
+    @staticmethod
+    def cleanup_feature_group(name):
+        """Delete the feature group if it already exists."""
+        try:
+            payload = {"featuregroups_list": [{"featureGroup_name": name}]}
+            resp = requests.delete(BASE_URL_SHORT, json=payload, timeout=5)
+            if resp.status_code == HTTPStatus.OK:
+                data = resp.json()
+                if data.get("success count", 0) > 0:
+                    print(f":broom: Cleaned existing FeatureGroup: {name}")
+        except Exception as e:
+            print(f":warning: Cleanup failed for {name}: {e}")
+    def test_create_feature_group(self, fg_name):
+        """Create a new Feature Group and verify response"""
+        create_payload = {
+            "featuregroup_name": fg_name,
+            "feature_list": "pdcpBytesDl,pdcpBytesUl",
+            "datalake_source": "InfluxSource",
+            "enable_dme": False,
+            "host": "my-release-influxdb.default",
+            "port": "8086",
+            "dme_port": "",
+            "bucket": "UEData",
+            "token": "OQURDmo86CK31aUKiXaN",
+            "source_name": "",
+            "measured_obj_class": "",
+            "measurement": "liveCell",
+            "db_org": "primary"
+        }
+        resp = requests.post(BASE_URL, json=create_payload)
+        if resp.status_code == HTTPStatus.CONFLICT:
+            data = resp.json()
+            assert "already exist" in data.get("detail", "")
+            print(f":warning: FeatureGroup {fg_name} already exists — skipping creation.")
+            return
+        assert resp.status_code in (HTTPStatus.OK, HTTPStatus.CREATED), \
+            f"Unexpected status: {resp.status_code}, body: {resp.text}"
+        data = resp.json()
+        assert data["featuregroup_name"] == fg_name
+        assert "id" in data
+        print(f":white_tick: Created FeatureGroup: {fg_name} (ID: {data['id']})")
+    def test_get_feature_groups(self, fg_name):
+        """Verify the created feature group appears in GET list"""
+        resp = requests.get(BASE_URL)
+        assert resp.status_code == HTTPStatus.OK, \
+            f"Unexpected status: {resp.status_code}, body: {resp.text}"
+        data = resp.json()
+        groups = data.get("FeatureGroups", [])
+        assert any(fg["featuregroup_name"] == fg_name for fg in groups), \
+            f"Feature group {fg_name} not found in list"
+        print(f":white_tick: Verified GET presence of FeatureGroup: {fg_name}")
+    def test_update_feature_group(self, fg_name):
+        """Update existing Feature Group and verify success"""
+        update_payload = {
+            "featuregroup_name": fg_name,
+            "feature_list": "pdcpBytesDl,pdcpBytesUl",
+            "datalake_source": "InfluxSource",
+            "enable_dme": False,
+            "host": "my-release-influxdb.default",
+            "port": "8086",
+            "dme_port": "",
+            "bucket": "UEData",
+            "token": "OQURDmo86CK31aUKiXaN",
+            "source_name": "",
+            "measured_obj_class": "",
+            "measurement": "liveCell2",
+            "db_org": "primary"
+        }
+        resp = requests.put(f"{BASE_URL_SHORT}/{fg_name}", json=update_payload)
+        assert resp.status_code == HTTPStatus.OK, \
+            f"Unexpected status: {resp.status_code}, body: {resp.text}"
+        data = resp.json()
+        assert data.get("result", "").lower().startswith("feature group edited")
+        print(f":white_tick: Updated FeatureGroup: {fg_name}")
+    def test_duplicate_feature_group_registration(self, fg_name):
+        """Attempt to create a duplicate Feature Group to validate 409 Conflict"""
+        payload = {
+            "featuregroup_name": fg_name,
+            "feature_list": "pdcpBytesDl,pdcpBytesUl",
+            "datalake_source": "InfluxSource",
+            "enable_dme": False,
+            "host": "my-release-influxdb.default",
+            "port": "8086",
+            "dme_port": "",
+            "bucket": "UEData",
+            "token": "OQURDmo86CK31aUKiXaN",
+            "source_name": "",
+            "measured_obj_class": "",
+            "measurement": "liveCell",
+            "db_org": "primary"
+        }
+        # First create
+        requests.post(BASE_URL, json=payload)
+        # Duplicate create
+        resp = requests.post(BASE_URL, json=payload)
+        assert resp.status_code == HTTPStatus.CONFLICT, \
+            f"Expected 409 Conflict, got {resp.status_code} ({resp.text})"
+        data = resp.json()
+        assert "already exist" in data.get("detail", "")
+        print(f":white_tick: Verified duplicate FeatureGroup {fg_name} returns 409 Conflict")
+    def test_delete_feature_group(self, fg_name):
+        """Delete the feature group and verify deletion"""
+        payload = {"featuregroups_list": [{"featureGroup_name": fg_name}]}
+        resp = requests.delete(BASE_URL_SHORT, json=payload)
+        assert resp.status_code == HTTPStatus.OK, \
+            f"Unexpected status: {resp.status_code}, body: {resp.text}"
+        data = resp.json()
+        assert data.get("success count", 0) >= 1
+        assert data.get("failure count", 0) == 0
+        print(f":white_tick: Deleted FeatureGroup: {fg_name}")
\ No newline at end of file
diff --git a/tox.ini b/tox.ini
index f1b0c32..52b1539 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -54,7 +54,7 @@ commands =
   pip3 install -e {toxinidir}
 
   pip3 install featurestoresdk modelmetricsdk
-  pytest --cov {toxinidir}/trainingmgr --cov-report xml --cov-report term-missing --cov-report html --cov-fail-under=10 --junitxml=/tmp/tests.xml
+  pytest --cov {toxinidir}/trainingmgr --cov-report xml --cov-report term-missing --cov-report html --cov-fail-under=10 --junitxml=/tmp/tests.xml --ignore={toxinidir}/component-testing
   coverage xml -i
 
 # Docs