From: Swaraj Kumar/Open Network Innovation /SRI-Bangalore/Engineer/Samsung Electronics Date: Tue, 25 Nov 2025 05:56:15 +0000 (+0530) Subject: Pytest for Feature Group CRUD Operations X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=aiml-fw%2Fawmf%2Ftm.git Pytest for Feature Group CRUD Operations 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 --- diff --git a/.github/workflows/gerrit-verify.yaml b/.github/workflows/gerrit-verify.yaml index 9b301e2..4f79d4d 100644 --- a/.github/workflows/gerrit-verify.yaml +++ b/.github/workflows/gerrit-verify.yaml @@ -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 index 0000000..76a20ca --- /dev/null +++ b/component-testing/tests/requirements.txt @@ -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 index 0000000..97a8e2d --- /dev/null +++ b/component-testing/tests/test_featuregroup.py @@ -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 --- 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