blob: ae9f803d8a8351b6dce98e52e67f594e044ce879 [file] [log] [blame]
/*
*
* 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.
*/
/**
* @file
* This file implements unit tests for CHIP Interaction Model Command Interaction
*
*/
#include <cinttypes>
#include <app/AppBuildConfig.h>
#include <app/InteractionModelEngine.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPTLV.h>
#include <lib/core/CHIPTLVDebug.hpp>
#include <lib/core/CHIPTLVUtilities.hpp>
#include <lib/support/ErrorStr.h>
#include <lib/support/UnitTestRegistration.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/ExchangeContext.h>
#include <messaging/ExchangeMgr.h>
#include <messaging/Flags.h>
#include <messaging/tests/MessagingContext.h>
#include <platform/CHIPDeviceLayer.h>
#include <protocols/interaction_model/Constants.h>
#include <protocols/secure_channel/MessageCounterManager.h>
#include <protocols/secure_channel/PASESession.h>
#include <system/SystemLayerImpl.h>
#include <system/SystemPacketBuffer.h>
#include <system/TLVPacketBufferBackingStore.h>
#include <transport/SessionHandle.h>
#include <transport/SessionManager.h>
#include <transport/raw/UDP.h>
#include <nlunit-test.h>
using namespace chip;
namespace chip {
namespace {
chip::TransportMgrBase gTransportManager;
chip::Test::LoopbackTransport gLoopback;
chip::Test::IOContext gIOContext;
Messaging::ExchangeManager * gExchangeManager;
secure_channel::MessageCounterManager gMessageCounterManager;
FabricIndex gFabricIndex = 0;
bool isCommandDispatched = false;
using TestContext = chip::Test::MessagingContext;
TestContext sContext;
bool sendResponse = true;
constexpr EndpointId kTestEndpointId = 1;
constexpr ClusterId kTestClusterId = 3;
constexpr CommandId kTestCommandId = 4;
constexpr CommandId kTestCommandIdCommandSpecificResponse = 5;
constexpr CommandId kTestNonExistCommandId = 0;
} // namespace
namespace app {
void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, chip::TLV::TLVReader & aReader,
CommandHandler * apCommandObj)
{
ChipLogDetail(Controller,
"Received Cluster Command: Endpoint=%" PRIx16 " Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
aCommandPath.mEndpointId, ChipLogValueMEI(aCommandPath.mClusterId), ChipLogValueMEI(aCommandPath.mCommandId));
if (sendResponse)
{
if (aCommandPath.mCommandId == kTestCommandId)
{
apCommandObj->AddStatus(aCommandPath, Protocols::InteractionModel::Status::Success);
}
else
{
CommandPathParams responseParams = { aCommandPath.mEndpointId, /* groupId */ 0, aCommandPath.mClusterId,
aCommandPath.mCommandId, (chip::app::CommandPathFlags::kEndpointIdValid) };
apCommandObj->PrepareCommand(responseParams);
chip::TLV::TLVWriter * writer = apCommandObj->GetCommandDataIBTLVWriter();
writer->PutBoolean(chip::TLV::ContextTag(1), true);
apCommandObj->FinishCommand();
}
}
chip::isCommandDispatched = true;
}
void DispatchSingleClusterResponseCommand(const ConcreteCommandPath & aCommandPath, chip::TLV::TLVReader & aReader,
CommandSender * apCommandObj)
{
ChipLogDetail(Controller,
"Received Cluster Command: Endpoint=%" PRIx16 " Cluster=" ChipLogFormatMEI " Command=" ChipLogFormatMEI,
aCommandPath.mEndpointId, ChipLogValueMEI(aCommandPath.mClusterId), ChipLogValueMEI(aCommandPath.mCommandId));
// Nothing todo.
}
class MockCommandSenderCallback : public CommandSender::Callback
{
public:
void OnResponse(chip::app::CommandSender * apCommandSender, const chip::app::ConcreteCommandPath & aPath,
chip::TLV::TLVReader * aData) override
{
IgnoreUnusedVariable(apCommandSender);
IgnoreUnusedVariable(aData);
ChipLogDetail(Controller, "Received Cluster Command: Cluster=%" PRIx32 " Command=%" PRIx32 " Endpoint=%" PRIx16,
aPath.mClusterId, aPath.mCommandId, aPath.mEndpointId);
onResponseCalledTimes++;
}
void OnError(const chip::app::CommandSender * apCommandSender, chip::Protocols::InteractionModel::Status aStatus,
CHIP_ERROR aError) override
{
ChipLogError(Controller, "OnError happens with %" PRIx16 " %" CHIP_ERROR_FORMAT, to_underlying(aStatus), aError.Format());
onErrorCalledTimes++;
}
void OnDone(chip::app::CommandSender * apCommandSender) override { onFinalCalledTimes++; }
void ResetCounter()
{
onResponseCalledTimes = 0;
onErrorCalledTimes = 0;
onFinalCalledTimes = 0;
}
int onResponseCalledTimes = 0;
int onErrorCalledTimes = 0;
int onFinalCalledTimes = 0;
} mockCommandSenderDelegate;
class MockCommandHandlerCallback : public CommandHandler::Callback
{
public:
void OnDone(chip::app::CommandHandler * apCommandHandler) final { onFinalCalledTimes++; }
int onFinalCalledTimes = 0;
} mockCommandHandlerDelegate;
bool ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath)
{
// Mock cluster catalog, only support one command on one cluster on one endpoint.
return (aCommandPath.mEndpointId == kTestEndpointId && aCommandPath.mClusterId == kTestClusterId &&
aCommandPath.mCommandId != kTestNonExistCommandId);
}
class TestCommandInteraction
{
public:
static void TestCommandSenderWithWrongState(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithWrongState(nlTestSuite * apSuite, void * apContext);
static void TestCommandSenderWithSendCommand(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithSendEmptyCommand(nlTestSuite * apSuite, void * apContext);
static void TestCommandSenderWithProcessReceivedMsg(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithProcessReceivedNotExistCommand(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithSendSimpleCommandData(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerCommandDataEncoding(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithSendSimpleStatusCode(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithSendEmptyResponse(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithProcessReceivedMsg(nlTestSuite * apSuite, void * apContext);
static void TestCommandHandlerWithProcessReceivedEmptyDataMsg(nlTestSuite * apSuite, void * apContext);
static void TestCommandSenderCommandSuccessResponseFlow(nlTestSuite * apSuite, void * apContext);
static void TestCommandSenderCommandFailureResponseFlow(nlTestSuite * apSuite, void * apContext);
static void TestCommandSenderCommandSpecificResponseFlow(nlTestSuite * apSuite, void * apContext);
static void TestCommandSenderAbruptDestruction(nlTestSuite * apSuite, void * apContext);
static size_t GetNumActiveHandlerObjects()
{
return chip::app::InteractionModelEngine::GetInstance()->mCommandHandlerObjs.Allocated();
}
private:
static void GenerateReceivedCommand(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload,
bool aNeedCommandData, EndpointId aEndpointId = kTestEndpointId,
ClusterId aClusterId = kTestClusterId, CommandId aCommandId = kTestCommandId);
static void AddCommandDataIB(nlTestSuite * apSuite, void * apContext, Command * apCommand, bool aNeedStatusCode,
CommandId aCommandId = kTestCommandId);
static void ValidateCommandHandlerWithSendCommand(nlTestSuite * apSuite, void * apContext, bool aNeedStatusCode);
};
class TestExchangeDelegate : public Messaging::ExchangeDelegate
{
CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && payload) override
{
return CHIP_NO_ERROR;
}
void OnResponseTimeout(Messaging::ExchangeContext * ec) override {}
};
CommandPathParams MakeTestCommandPath(CommandId aCommandId = kTestCommandId)
{
return CommandPathParams(kTestEndpointId, 0, kTestClusterId, aCommandId, (chip::app::CommandPathFlags::kEndpointIdValid));
}
void TestCommandInteraction::GenerateReceivedCommand(nlTestSuite * apSuite, void * apContext, System::PacketBufferHandle & aPayload,
bool aNeedCommandData, EndpointId aEndpointId, ClusterId aClusterId,
CommandId aCommandId)
{
CHIP_ERROR err = CHIP_NO_ERROR;
InvokeCommand::Builder invokeCommandBuilder;
System::PacketBufferTLVWriter writer;
writer.Init(std::move(aPayload));
err = invokeCommandBuilder.Init(&writer);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
CommandList::Builder commandList = invokeCommandBuilder.CreateCommandListBuilder();
NL_TEST_ASSERT(apSuite, invokeCommandBuilder.GetError() == CHIP_NO_ERROR);
CommandDataIB::Builder commandDataIBBuilder = commandList.CreateCommandDataIBBuilder();
NL_TEST_ASSERT(apSuite, commandList.GetError() == CHIP_NO_ERROR);
CommandPathIB::Builder commandPathBuilder = commandDataIBBuilder.CreateCommandPathBuilder();
NL_TEST_ASSERT(apSuite, commandDataIBBuilder.GetError() == CHIP_NO_ERROR);
commandPathBuilder.EndpointId(aEndpointId).ClusterId(aClusterId).CommandId(aCommandId).EndOfCommandPath();
NL_TEST_ASSERT(apSuite, commandPathBuilder.GetError() == CHIP_NO_ERROR);
if (aNeedCommandData)
{
chip::TLV::TLVWriter * pWriter = commandDataIBBuilder.GetWriter();
chip::TLV::TLVType dummyType = chip::TLV::kTLVType_NotSpecified;
err = pWriter->StartContainer(chip::TLV::ContextTag(CommandDataIB::kCsTag_Data), chip::TLV::kTLVType_Structure, dummyType);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = pWriter->PutBoolean(chip::TLV::ContextTag(1), true);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = pWriter->EndContainer(dummyType);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
}
commandDataIBBuilder.EndOfCommandDataIB();
NL_TEST_ASSERT(apSuite, commandDataIBBuilder.GetError() == CHIP_NO_ERROR);
commandList.EndOfCommandList();
NL_TEST_ASSERT(apSuite, commandList.GetError() == CHIP_NO_ERROR);
invokeCommandBuilder.EndOfInvokeCommand();
NL_TEST_ASSERT(apSuite, invokeCommandBuilder.GetError() == CHIP_NO_ERROR);
err = writer.Finalize(&aPayload);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
}
void TestCommandInteraction::AddCommandDataIB(nlTestSuite * apSuite, void * apContext, Command * apCommand, bool aNeedStatusCode,
CommandId aCommandId)
{
CHIP_ERROR err = CHIP_NO_ERROR;
auto commandPathParams = MakeTestCommandPath(aCommandId);
if (aNeedStatusCode)
{
chip::app::ConcreteCommandPath commandPath(1, // Endpoint
3, // ClusterId
4 // CommandId
);
apCommand->AddStatus(commandPath, Protocols::InteractionModel::Status::Success);
}
else
{
err = apCommand->PrepareCommand(commandPathParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
chip::TLV::TLVWriter * writer = apCommand->GetCommandDataIBTLVWriter();
err = writer->PutBoolean(chip::TLV::ContextTag(1), true);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = apCommand->FinishCommand();
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
}
}
void TestCommandInteraction::TestCommandSenderWithWrongState(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandSender commandSender(&mockCommandSenderDelegate, gExchangeManager);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = commandSender.SendCommandRequest(kTestDeviceNodeId, gFabricIndex, Optional<SessionHandle>::Missing());
NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_INCORRECT_STATE);
}
void TestCommandInteraction::TestCommandHandlerWithWrongState(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
auto commandPathParams = MakeTestCommandPath();
app::CommandHandler commandHandler(&mockCommandHandlerDelegate);
err = commandHandler.PrepareCommand(commandPathParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
auto exchangeCtx = gExchangeManager->NewContext(SessionHandle(0, 0, 0, 0), nullptr);
commandHandler.mpExchangeCtx = exchangeCtx;
TestExchangeDelegate delegate;
commandHandler.mpExchangeCtx->SetDelegate(&delegate);
err = commandHandler.SendCommandResponse();
NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_INCORRECT_STATE);
}
void TestCommandInteraction::TestCommandSenderWithSendCommand(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandSender commandSender(&mockCommandSenderDelegate, gExchangeManager);
System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
AddCommandDataIB(apSuite, apContext, &commandSender, false);
err = commandSender.SendCommandRequest(kTestDeviceNodeId, gFabricIndex, Optional<SessionHandle>::Missing());
NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_NOT_CONNECTED);
GenerateReceivedCommand(apSuite, apContext, buf, true /*aNeedCommandData*/);
err = commandSender.ProcessCommandMessage(std::move(buf), Command::CommandRoleId::SenderId);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
}
void TestCommandInteraction::TestCommandHandlerWithSendEmptyCommand(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
auto commandPathParams = MakeTestCommandPath();
app::CommandHandler commandHandler(&mockCommandHandlerDelegate);
System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
auto exchangeCtx = gExchangeManager->NewContext(SessionHandle(0, 0, 0, 0), nullptr);
commandHandler.mpExchangeCtx = exchangeCtx;
TestExchangeDelegate delegate;
commandHandler.mpExchangeCtx->SetDelegate(&delegate);
err = commandHandler.PrepareCommand(commandPathParams);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = commandHandler.FinishCommand();
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = commandHandler.SendCommandResponse();
NL_TEST_ASSERT(apSuite, err == CHIP_ERROR_NOT_CONNECTED);
}
void TestCommandInteraction::TestCommandSenderWithProcessReceivedMsg(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandSender commandSender(&mockCommandSenderDelegate, gExchangeManager);
System::PacketBufferHandle buf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
GenerateReceivedCommand(apSuite, apContext, buf, true /*aNeedCommandData*/);
err = commandSender.ProcessCommandMessage(std::move(buf), Command::CommandRoleId::SenderId);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
}
void TestCommandInteraction::ValidateCommandHandlerWithSendCommand(nlTestSuite * apSuite, void * apContext, bool aNeedStatusCode)
{
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandHandler commandHandler(&mockCommandHandlerDelegate);
System::PacketBufferHandle commandPacket;
auto exchangeCtx = gExchangeManager->NewContext(SessionHandle(0, 0, 0, 0), nullptr);
commandHandler.mpExchangeCtx = exchangeCtx;
TestExchangeDelegate delegate;
commandHandler.mpExchangeCtx->SetDelegate(&delegate);
AddCommandDataIB(apSuite, apContext, &commandHandler, aNeedStatusCode);
err = commandHandler.Finalize(commandPacket);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
chip::System::PacketBufferTLVReader reader;
InvokeCommand::Parser invokeCommandParser;
reader.Init(std::move(commandPacket));
err = reader.Next();
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = invokeCommandParser.Init(reader);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = invokeCommandParser.CheckSchemaValidity();
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
#endif
}
void TestCommandInteraction::TestCommandHandlerWithSendSimpleCommandData(nlTestSuite * apSuite, void * apContext)
{
// Send response which has simple command data and command path
ValidateCommandHandlerWithSendCommand(apSuite, apContext, false /*aNeedStatusCode=false*/);
}
struct Fields
{
static constexpr chip::CommandId GetCommandId() { return 4; }
CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const
{
TLV::TLVType outerContainerType;
ReturnErrorOnFailure(aWriter.StartContainer(aTag, TLV::kTLVType_Structure, outerContainerType));
ReturnErrorOnFailure(aWriter.PutBoolean(TLV::ContextTag(1), true));
return aWriter.EndContainer(outerContainerType);
}
};
void TestCommandInteraction::TestCommandHandlerCommandDataEncoding(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandHandler commandHandler(nullptr);
System::PacketBufferHandle commandPacket;
commandHandler.mpExchangeCtx = gExchangeManager->NewContext(SessionHandle(0, 0, 0, 0), nullptr);
TestExchangeDelegate delegate;
commandHandler.mpExchangeCtx->SetDelegate(&delegate);
auto path = MakeTestCommandPath();
err = commandHandler.AddResponseData(ConcreteCommandPath(path.mEndpointId, path.mClusterId, path.mCommandId), Fields());
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = commandHandler.Finalize(commandPacket);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
#if CHIP_CONFIG_IM_ENABLE_SCHEMA_CHECK
chip::System::PacketBufferTLVReader reader;
InvokeCommand::Parser invokeCommandParser;
reader.Init(std::move(commandPacket));
err = reader.Next();
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = invokeCommandParser.Init(reader);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
err = invokeCommandParser.CheckSchemaValidity();
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
#endif
}
void TestCommandInteraction::TestCommandHandlerWithSendSimpleStatusCode(nlTestSuite * apSuite, void * apContext)
{
// Send response which has simple status code and command path
ValidateCommandHandlerWithSendCommand(apSuite, apContext, true /*aNeedStatusCode=true*/);
}
void TestCommandInteraction::TestCommandHandlerWithProcessReceivedMsg(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandHandler commandHandler(&mockCommandHandlerDelegate);
System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
GenerateReceivedCommand(apSuite, apContext, commandDatabuf, true /*aNeedCommandData*/);
err = commandHandler.ProcessCommandMessage(std::move(commandDatabuf), Command::CommandRoleId::HandlerId);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
}
void TestCommandInteraction::TestCommandHandlerWithProcessReceivedNotExistCommand(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandHandler commandHandler(&mockCommandHandlerDelegate);
System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
// Use some invalid endpoint / cluster / command.
GenerateReceivedCommand(apSuite, apContext, commandDatabuf, false /*aNeedCommandData*/, 0xDE /* endpoint */,
0xADBE /* cluster */, 0xEF /* command */);
// TODO: Need to find a way to get the response instead of only check if a function on key path is called.
// We should not reach CommandDispatch if requested command does not exist.
chip::isCommandDispatched = false;
err = commandHandler.ProcessCommandMessage(std::move(commandDatabuf), Command::CommandRoleId::HandlerId);
NL_TEST_ASSERT(apSuite, !chip::isCommandDispatched);
}
void TestCommandInteraction::TestCommandHandlerWithProcessReceivedEmptyDataMsg(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
app::CommandHandler commandHandler(&mockCommandHandlerDelegate);
System::PacketBufferHandle commandDatabuf = System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize);
chip::isCommandDispatched = false;
GenerateReceivedCommand(apSuite, apContext, commandDatabuf, false /*aNeedCommandData*/);
err = commandHandler.ProcessCommandMessage(std::move(commandDatabuf), Command::CommandRoleId::HandlerId);
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR && chip::isCommandDispatched);
}
void TestCommandInteraction::TestCommandSenderCommandSuccessResponseFlow(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TestContext & ctx = *static_cast<TestContext *>(apContext);
mockCommandSenderDelegate.ResetCounter();
app::CommandSender commandSender(&mockCommandSenderDelegate, gExchangeManager);
AddCommandDataIB(apSuite, apContext, &commandSender, false);
err = commandSender.SendCommandRequest(0, 0, Optional<SessionHandle>(ctx.GetSessionBobToAlice()));
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite,
mockCommandSenderDelegate.onResponseCalledTimes == 1 && mockCommandSenderDelegate.onFinalCalledTimes == 1 &&
mockCommandSenderDelegate.onErrorCalledTimes == 0);
NL_TEST_ASSERT(apSuite, GetNumActiveHandlerObjects() == 0);
NL_TEST_ASSERT(apSuite, gExchangeManager->GetNumActiveExchanges() == 0);
}
void TestCommandInteraction::TestCommandSenderCommandSpecificResponseFlow(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TestContext & ctx = *static_cast<TestContext *>(apContext);
mockCommandSenderDelegate.ResetCounter();
app::CommandSender commandSender(&mockCommandSenderDelegate, gExchangeManager);
AddCommandDataIB(apSuite, apContext, &commandSender, false, kTestCommandIdCommandSpecificResponse);
err = commandSender.SendCommandRequest(0, 0, Optional<SessionHandle>(ctx.GetSessionBobToAlice()));
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite,
mockCommandSenderDelegate.onResponseCalledTimes == 1 && mockCommandSenderDelegate.onFinalCalledTimes == 1 &&
mockCommandSenderDelegate.onErrorCalledTimes == 0);
NL_TEST_ASSERT(apSuite, GetNumActiveHandlerObjects() == 0);
NL_TEST_ASSERT(apSuite, gExchangeManager->GetNumActiveExchanges() == 0);
}
void TestCommandInteraction::TestCommandSenderCommandFailureResponseFlow(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TestContext & ctx = *static_cast<TestContext *>(apContext);
mockCommandSenderDelegate.ResetCounter();
app::CommandSender commandSender(&mockCommandSenderDelegate, gExchangeManager);
AddCommandDataIB(apSuite, apContext, &commandSender, false, kTestNonExistCommandId);
err = commandSender.SendCommandRequest(0, 0, Optional<SessionHandle>(ctx.GetSessionBobToAlice()));
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
NL_TEST_ASSERT(apSuite,
mockCommandSenderDelegate.onResponseCalledTimes == 0 && mockCommandSenderDelegate.onFinalCalledTimes == 1 &&
mockCommandSenderDelegate.onErrorCalledTimes == 1);
NL_TEST_ASSERT(apSuite, GetNumActiveHandlerObjects() == 0);
NL_TEST_ASSERT(apSuite, gExchangeManager->GetNumActiveExchanges() == 0);
}
void TestCommandInteraction::TestCommandSenderAbruptDestruction(nlTestSuite * apSuite, void * apContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TestContext & ctx = *static_cast<TestContext *>(apContext);
//
// Don't send back a response, just keep the CommandHandler
// hanging to give us enough time to do what we want with the CommandSender object.
//
sendResponse = false;
mockCommandSenderDelegate.ResetCounter();
{
app::CommandSender commandSender(&mockCommandSenderDelegate, gExchangeManager);
AddCommandDataIB(apSuite, apContext, &commandSender, false, kTestCommandIdCommandSpecificResponse);
err = commandSender.SendCommandRequest(0, 0, Optional<SessionHandle>(ctx.GetSessionBobToAlice()));
NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR);
//
// No callbacks should be invoked yet - let's validate that.
//
NL_TEST_ASSERT(apSuite,
mockCommandSenderDelegate.onResponseCalledTimes == 0 && mockCommandSenderDelegate.onFinalCalledTimes == 0 &&
mockCommandSenderDelegate.onErrorCalledTimes == 0);
NL_TEST_ASSERT(apSuite, gExchangeManager->GetNumActiveExchanges() == 1);
}
//
// Upon the sender being destructed by the application, our exchange should get cleaned up too.
//
NL_TEST_ASSERT(apSuite, gExchangeManager->GetNumActiveExchanges() == 0);
NL_TEST_ASSERT(apSuite, GetNumActiveHandlerObjects() == 0);
}
} // namespace app
} // namespace chip
namespace {
// clang-format off
const nlTest sTests[] =
{
NL_TEST_DEF("TestCommandSenderWithWrongState", chip::app::TestCommandInteraction::TestCommandSenderWithWrongState),
NL_TEST_DEF("TestCommandHandlerWithWrongState", chip::app::TestCommandInteraction::TestCommandHandlerWithWrongState),
NL_TEST_DEF("TestCommandSenderWithSendCommand", chip::app::TestCommandInteraction::TestCommandSenderWithSendCommand),
NL_TEST_DEF("TestCommandHandlerWithSendEmptyCommand", chip::app::TestCommandInteraction::TestCommandHandlerWithSendEmptyCommand),
NL_TEST_DEF("TestCommandSenderWithProcessReceivedMsg", chip::app::TestCommandInteraction::TestCommandSenderWithProcessReceivedMsg),
NL_TEST_DEF("TestCommandHandlerWithSendSimpleCommandData", chip::app::TestCommandInteraction::TestCommandHandlerWithSendSimpleCommandData),
NL_TEST_DEF("TestCommandHandlerCommandDataEncoding", chip::app::TestCommandInteraction::TestCommandHandlerCommandDataEncoding),
NL_TEST_DEF("TestCommandHandlerWithSendSimpleStatusCode", chip::app::TestCommandInteraction::TestCommandHandlerWithSendSimpleStatusCode),
NL_TEST_DEF("TestCommandHandlerWithProcessReceivedMsg", chip::app::TestCommandInteraction::TestCommandHandlerWithProcessReceivedMsg),
NL_TEST_DEF("TestCommandHandlerWithProcessReceivedNotExistCommand", chip::app::TestCommandInteraction::TestCommandHandlerWithProcessReceivedNotExistCommand),
NL_TEST_DEF("TestCommandHandlerWithProcessReceivedEmptyDataMsg", chip::app::TestCommandInteraction::TestCommandHandlerWithProcessReceivedEmptyDataMsg),
NL_TEST_DEF("TestCommandSenderCommandSuccessResponseFlow", chip::app::TestCommandInteraction::TestCommandSenderCommandSuccessResponseFlow),
NL_TEST_DEF("TestCommandSenderCommandSpecificResponseFlow", chip::app::TestCommandInteraction::TestCommandSenderCommandSpecificResponseFlow),
NL_TEST_DEF("TestCommandSenderCommandFailureResponseFlow", chip::app::TestCommandInteraction::TestCommandSenderCommandFailureResponseFlow),
NL_TEST_DEF("TestCommandSenderAbruptDestruction", chip::app::TestCommandInteraction::TestCommandSenderAbruptDestruction),
NL_TEST_SENTINEL()
};
// clang-format on
int Initialize(void * aContext);
int Finalize(void * aContext);
// clang-format off
nlTestSuite sSuite =
{
"TestReadInteraction",
&sTests[0],
Initialize,
Finalize
};
// clang-format on
int Initialize(void * aContext)
{
// Initialize System memory and resources
VerifyOrReturnError(chip::Platform::MemoryInit() == CHIP_NO_ERROR, FAILURE);
VerifyOrReturnError(gIOContext.Init(&sSuite) == CHIP_NO_ERROR, FAILURE);
VerifyOrReturnError(gTransportManager.Init(&gLoopback) == CHIP_NO_ERROR, FAILURE);
auto * ctx = static_cast<TestContext *>(aContext);
VerifyOrReturnError(ctx->Init(&sSuite, &gTransportManager, &gIOContext) == CHIP_NO_ERROR, FAILURE);
gTransportManager.SetSessionManager(&ctx->GetSecureSessionManager());
gExchangeManager = &ctx->GetExchangeManager();
VerifyOrReturnError(
chip::app::InteractionModelEngine::GetInstance()->Init(&ctx->GetExchangeManager(), nullptr) == CHIP_NO_ERROR, FAILURE);
return SUCCESS;
}
int Finalize(void * aContext)
{
// Shutdown will ensure no leaked exchange context.
CHIP_ERROR err = reinterpret_cast<TestContext *>(aContext)->Shutdown();
gIOContext.Shutdown();
chip::Platform::MemoryShutdown();
return (err == CHIP_NO_ERROR) ? SUCCESS : FAILURE;
}
} // namespace
int TestCommandInteraction()
{
nlTestRunner(&sSuite, &sContext);
return (nlTestRunnerStats(&sSuite));
}
CHIP_REGISTER_TEST_SUITE(TestCommandInteraction)