| /* |
| * |
| * Copyright (c) 2021 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. |
| */ |
| |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <app/InteractionModelEngine.h> |
| #include <app/tests/AppTestContext.h> |
| #include <controller/ReadInteraction.h> |
| #include <lib/support/ErrorStr.h> |
| #include <lib/support/UnitTestRegistration.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <messaging/tests/MessagingContext.h> |
| #include <nlunit-test.h> |
| |
| using TestContext = chip::Test::AppContext; |
| |
| using namespace chip; |
| using namespace chip::app::Clusters; |
| |
| namespace { |
| |
| constexpr EndpointId kTestEndpointId = 1; |
| |
| enum ResponseDirective |
| { |
| kSendDataResponse, |
| kSendDataError |
| }; |
| |
| ResponseDirective responseDirective; |
| |
| } // namespace |
| |
| namespace chip { |
| namespace app { |
| |
| void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, chip::TLV::TLVReader & aReader, |
| CommandHandler * apCommandObj) |
| {} |
| |
| bool ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath) |
| { |
| // Mock cluster catalog, only support one command on one cluster on one endpoint. |
| return (aCommandPath.mEndpointId == kTestEndpointId && aCommandPath.mClusterId == TestCluster::Id); |
| } |
| |
| CHIP_ERROR ReadSingleClusterData(FabricIndex aAccessingFabricIndex, const ConcreteReadAttributePath & aPath, |
| AttributeReportIBs::Builder & aAttributeReports, |
| AttributeValueEncoder::AttributeEncodeState * apEncoderState) |
| { |
| |
| if (responseDirective == kSendDataResponse) |
| { |
| auto attributeReport = aAttributeReports.CreateAttributeReport(); |
| AttributeDataIB::Builder attributeData = attributeReport.CreateAttributeData(); |
| TestCluster::Attributes::ListStructOctetString::TypeInfo::Type value; |
| TestCluster::Structs::TestListStructOctet::Type valueBuf[4]; |
| |
| value = valueBuf; |
| |
| uint8_t i = 0; |
| for (auto & item : valueBuf) |
| { |
| item.fabricIndex = i; |
| i++; |
| } |
| |
| attributeData.DataVersion(0); |
| AttributePathIB::Builder attributePath = attributeData.CreatePath(); |
| attributePath.Endpoint(aPath.mEndpointId).Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId).EndOfAttributePathIB(); |
| ReturnErrorOnFailure(attributePath.GetError()); |
| |
| ReturnErrorOnFailure(DataModel::Encode(*(attributeData.GetWriter()), |
| chip::TLV::ContextTag(chip::to_underlying(AttributeDataIB::Tag::kData)), value)); |
| ReturnErrorOnFailure(attributeData.EndOfAttributeDataIB().GetError()); |
| return attributeReport.EndOfAttributeReportIB().GetError(); |
| } |
| else |
| { |
| auto attributeReport = aAttributeReports.CreateAttributeReport(); |
| AttributeStatusIB::Builder attributeStatus = attributeReport.CreateAttributeStatus(); |
| AttributePathIB::Builder attributePath = attributeStatus.CreatePath(); |
| attributePath.Endpoint(aPath.mEndpointId).Cluster(aPath.mClusterId).Attribute(aPath.mAttributeId).EndOfAttributePathIB(); |
| ReturnErrorOnFailure(attributePath.GetError()); |
| |
| StatusIB::Builder errorStatus = attributeStatus.CreateErrorStatus(); |
| errorStatus.EncodeStatusIB(StatusIB(Protocols::InteractionModel::Status::Busy)); |
| attributeStatus.EndOfAttributeStatusIB(); |
| ReturnErrorOnFailure(attributeStatus.GetError()); |
| return attributeReport.EndOfAttributeReportIB().GetError(); |
| } |
| |
| return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; |
| } |
| |
| CHIP_ERROR WriteSingleClusterData(ClusterInfo & aClusterInfo, TLV::TLVReader & aReader, WriteHandler * aWriteHandler) |
| { |
| return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; |
| } |
| |
| } // namespace app |
| } // namespace chip |
| |
| namespace { |
| |
| class TestReadInteraction |
| { |
| public: |
| TestReadInteraction() {} |
| |
| static void TestDataResponse(nlTestSuite * apSuite, void * apContext); |
| static void TestAttributeError(nlTestSuite * apSuite, void * apContext); |
| static void TestReadTimeout(nlTestSuite * apSuite, void * apContext); |
| |
| private: |
| }; |
| |
| void TestReadInteraction::TestDataResponse(nlTestSuite * apSuite, void * apContext) |
| { |
| TestContext & ctx = *static_cast<TestContext *>(apContext); |
| auto sessionHandle = ctx.GetSessionBobToAlice(); |
| bool onSuccessCbInvoked = false, onFailureCbInvoked = false; |
| |
| responseDirective = kSendDataResponse; |
| |
| // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's |
| // not safe to do so. |
| auto onSuccessCb = [apSuite, &onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { |
| uint8_t i = 0; |
| |
| auto iter = dataResponse.begin(); |
| while (iter.Next()) |
| { |
| auto & item = iter.GetValue(); |
| NL_TEST_ASSERT(apSuite, item.fabricIndex == i); |
| i++; |
| } |
| |
| NL_TEST_ASSERT(apSuite, i == 4); |
| NL_TEST_ASSERT(apSuite, iter.GetStatus() == CHIP_NO_ERROR); |
| |
| onSuccessCbInvoked = true; |
| }; |
| |
| // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's |
| // not safe to do so. |
| auto onFailureCb = [&onFailureCbInvoked](const app::ConcreteAttributePath * attributePath, app::StatusIB aIMStatus, |
| CHIP_ERROR aError) { onFailureCbInvoked = true; }; |
| |
| chip::Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>( |
| &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); |
| |
| ctx.DrainAndServiceIO(); |
| chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); |
| ctx.DrainAndServiceIO(); |
| |
| NL_TEST_ASSERT(apSuite, onSuccessCbInvoked && !onFailureCbInvoked); |
| NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); |
| NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); |
| NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); |
| } |
| |
| void TestReadInteraction::TestAttributeError(nlTestSuite * apSuite, void * apContext) |
| { |
| TestContext & ctx = *static_cast<TestContext *>(apContext); |
| auto sessionHandle = ctx.GetSessionBobToAlice(); |
| bool onSuccessCbInvoked = false, onFailureCbInvoked = false; |
| |
| responseDirective = kSendDataError; |
| |
| // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's |
| // not safe to do so. |
| auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { |
| onSuccessCbInvoked = true; |
| }; |
| |
| // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's |
| // not safe to do so. |
| auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteAttributePath * attributePath, |
| Protocols::InteractionModel::Status aIMStatus, CHIP_ERROR aError) { |
| NL_TEST_ASSERT(apSuite, (aError != CHIP_NO_ERROR) && (aIMStatus == Protocols::InteractionModel::Status::Busy)); |
| onFailureCbInvoked = true; |
| }; |
| |
| chip::Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>( |
| &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); |
| |
| ctx.DrainAndServiceIO(); |
| chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); |
| ctx.DrainAndServiceIO(); |
| |
| NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked); |
| NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); |
| NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); |
| NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); |
| } |
| |
| void TestReadInteraction::TestReadTimeout(nlTestSuite * apSuite, void * apContext) |
| { |
| TestContext & ctx = *static_cast<TestContext *>(apContext); |
| auto sessionHandle = ctx.GetSessionBobToAlice(); |
| bool onSuccessCbInvoked = false, onFailureCbInvoked = false; |
| |
| responseDirective = kSendDataError; |
| |
| // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's |
| // not safe to do so. |
| auto onSuccessCb = [&onSuccessCbInvoked](const app::ConcreteAttributePath & attributePath, const auto & dataResponse) { |
| onSuccessCbInvoked = true; |
| }; |
| |
| // Passing of stack variables by reference is only safe because of synchronous completion of the interaction. Otherwise, it's |
| // not safe to do so. |
| auto onFailureCb = [&onFailureCbInvoked, apSuite](const app::ConcreteAttributePath * attributePath, |
| Protocols::InteractionModel::Status aIMStatus, CHIP_ERROR aError) { |
| NL_TEST_ASSERT(apSuite, aError == CHIP_ERROR_TIMEOUT); |
| onFailureCbInvoked = true; |
| }; |
| |
| chip::Controller::ReadAttribute<TestCluster::Attributes::ListStructOctetString::TypeInfo>( |
| &ctx.GetExchangeManager(), sessionHandle, kTestEndpointId, onSuccessCb, onFailureCb); |
| |
| ctx.DrainAndServiceIO(); |
| |
| NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 1); |
| NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 2); |
| |
| ctx.GetExchangeManager().ExpireExchangesForSession(ctx.GetSessionBobToAlice()); |
| |
| ctx.DrainAndServiceIO(); |
| |
| NL_TEST_ASSERT(apSuite, !onSuccessCbInvoked && onFailureCbInvoked); |
| NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadClients() == 0); |
| |
| // |
| // TODO: Figure out why I cannot enable this line below. |
| // |
| // NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 1); |
| |
| ctx.DrainAndServiceIO(); |
| chip::app::InteractionModelEngine::GetInstance()->GetReportingEngine().Run(); |
| ctx.DrainAndServiceIO(); |
| |
| ctx.GetExchangeManager().ExpireExchangesForSession(ctx.GetSessionAliceToBob()); |
| |
| NL_TEST_ASSERT(apSuite, chip::app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == 0); |
| |
| // |
| // TODO: Figure out why I cannot enable this line below. |
| // |
| // NL_TEST_ASSERT(apSuite, ctx.GetExchangeManager().GetNumActiveExchanges() == 0); |
| } |
| |
| // clang-format off |
| const nlTest sTests[] = |
| { |
| NL_TEST_DEF("TestDataResponse", TestReadInteraction::TestDataResponse), |
| NL_TEST_DEF("TestAttributeError", TestReadInteraction::TestAttributeError), |
| NL_TEST_DEF("TestReadTimeout", TestReadInteraction::TestReadTimeout), |
| NL_TEST_SENTINEL() |
| }; |
| // clang-format on |
| |
| // clang-format off |
| nlTestSuite sSuite = |
| { |
| "TestRead", |
| &sTests[0], |
| TestContext::InitializeAsync, |
| TestContext::Finalize |
| }; |
| // clang-format on |
| |
| } // namespace |
| |
| int TestReadInteractionTest() |
| { |
| TestContext gContext; |
| nlTestRunner(&sSuite, &gContext); |
| return (nlTestRunnerStats(&sSuite)); |
| } |
| |
| CHIP_REGISTER_TEST_SUITE(TestReadInteractionTest) |