blob: be4153cd9318220de897c9a220641024eb2be692 [file]
#
# Copyright (c) 2025 Project CHIP Authors
# 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.
#
# This test requires an all-clusters-app. Specify with --string-arg th_server_app_path:<path_to_app>
#
# Example invocation:
# python src/python_testing/test_testing/TestCommissioningStatusDetectionIntegration.py \
# --string-arg th_server_app_path:./out/linux-x64-all-clusters-no-ble/chip-all-clusters-app
#
# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments
# for details about the block below.
#
# === BEGIN CI TEST ARGUMENTS ===
# test-runner-runs:
# run1:
# script-args: >
# --storage-path admin_storage.json
# --string-arg th_server_app_path:${ALL_CLUSTERS_APP}
# --trace-to json:${TRACE_TEST_JSON}.json
# --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto
# factory-reset: true
# quiet: true
# === END CI TEST ARGUMENTS ===
"""
Integration tests for commissioning status detection functions.
These tests verify that the commissioning status detection functions work correctly
with a real Matter application server.
"""
import asyncio
import logging
import os
import tempfile
from mobly import asserts
from python_path import PythonPath
from matter import ChipDeviceCtrl
from matter.testing.apps import AppServerSubprocess
from matter.testing.commissioning import _is_device_operational_via_dnssd, is_commissioned
from matter.testing.decorators import async_test_body
from matter.testing.matter_testing import MatterBaseTest
from matter.testing.runner import default_matter_test_main
# Add python_testing directory to path so mdns_discovery module is available
with PythonPath('..', relative_to=__file__):
import mdns_discovery.mdns_discovery # noqa: F401
LOGGER = logging.getLogger(__name__)
class TestCommissioningStatusDetectionIntegration(MatterBaseTest):
"""
Integration tests for commissioning status detection with real Matter apps.
Follows TC_SC_3_5 pattern for server management and commissioning.
"""
def setup_class(self):
super().setup_class()
self.th_server = None
self.storage = None
# Use fixed values like TC_SC_3_5
self.th_server_local_nodeid = 1111
self.th_server_discriminator = 1234
self.th_server_passcode = 20202021
# Get the path to the test app
self.th_server_app = self.user_params.get("th_server_app_path", None)
if not self.th_server_app:
asserts.fail(
"This test requires an all-clusters-app. "
"Specify app path with --string-arg th_server_app_path:<path_to_app>"
)
if not os.path.exists(self.th_server_app):
asserts.fail(f"The path {self.th_server_app} does not exist")
def teardown_class(self):
if self.th_server is not None:
self.th_server.terminate()
if self.storage is not None:
self.storage.cleanup()
super().teardown_class()
def start_th_server(self):
"""Start the TH server app (following TC_SC_3_5 pattern)."""
if self.th_server is not None:
self.th_server.terminate()
if self.storage is not None:
self.storage.cleanup()
# Expire any existing sessions for this node to ensure we start fresh
# This prevents the controller from trying to use a stale session from a previous test run
if self.default_controller:
self.default_controller.ExpireSessions(self.th_server_local_nodeid)
# Create a temporary storage directory for KVS files
self.storage = tempfile.TemporaryDirectory(prefix=self.__class__.__name__)
LOGGER.info("Temporary storage directory: %s", self.storage.name)
self.th_server = AppServerSubprocess(
self.th_server_app,
storage_dir=self.storage.name,
discriminator=self.th_server_discriminator,
passcode=self.th_server_passcode
)
self.th_server.start(
expected_output="Server initialization complete",
timeout=60
)
LOGGER.info("TH server started successfully")
@async_test_body
async def test_TC_COMMISSION_DETECT_1_1_factory_fresh_dnssd(self):
"""
Test that DNS-SD check returns False for factory-fresh device.
"""
LOGGER.info("=== Test: Factory Fresh Device - DNS-SD Check ===")
self.start_th_server()
LOGGER.info("Checking DNS-SD for factory-fresh device")
is_operational = await _is_device_operational_via_dnssd(
self.default_controller,
self.th_server_local_nodeid
)
asserts.assert_false(
is_operational,
"Factory-fresh device should NOT be found operational via DNS-SD"
)
LOGGER.info("PASS: Factory-fresh device not found operational via DNS-SD")
@async_test_body
async def test_TC_COMMISSION_DETECT_1_2_factory_fresh_is_commissioned(self):
"""
Test that is_commissioned() returns False for factory-fresh device.
"""
LOGGER.info("=== Test: Factory Fresh Device - is_commissioned() ===")
self.start_th_server()
LOGGER.info("Checking is_commissioned() on factory-fresh device")
commissioned = await is_commissioned(
self.default_controller,
self.th_server_local_nodeid
)
asserts.assert_false(
commissioned,
"Factory-fresh device should report is_commissioned=False"
)
LOGGER.info("PASS: Factory-fresh device reports is_commissioned=False")
@async_test_body
async def test_TC_COMMISSION_DETECT_1_3_commissioned_dnssd(self):
"""
Test that DNS-SD finds a commissioned device as operational.
"""
LOGGER.info("=== Test: Commissioned Device - DNS-SD Check ===")
self.start_th_server()
# Commission device (same pattern as TC_SC_3_5)
LOGGER.info("Commissioning TH server to TH fabric")
await self.default_controller.CommissionOnNetwork(
nodeId=self.th_server_local_nodeid,
setupPinCode=self.th_server_passcode,
filterType=ChipDeviceCtrl.DiscoveryFilterType.LONG_DISCRIMINATOR,
filter=self.th_server_discriminator
)
LOGGER.info("Commissioning TH_SERVER complete")
# Wait for mDNS advertisement to propagate
await asyncio.sleep(2)
LOGGER.info("Checking DNS-SD for commissioned device")
# Retry a few times for flaky mDNS
is_operational = False
for attempt in range(3):
is_operational = await _is_device_operational_via_dnssd(
self.default_controller,
self.th_server_local_nodeid
)
if is_operational:
break
LOGGER.info("DNS-SD check attempt %s returned False, retrying...", attempt + 1)
await asyncio.sleep(2)
asserts.assert_true(
is_operational,
"Commissioned device SHOULD be found operational via DNS-SD"
)
LOGGER.info("PASS: Commissioned device found operational via DNS-SD")
if __name__ == "__main__":
default_matter_test_main()