blob: c54e458ad81dde7e5ebcb84f8bdaadd19abf809e [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 <lib/core/StringBuilderAdapters.h>
#include <pw_unit_test/framework.h>
#include "DataModelFixtures.h"
#include <app-common/zap-generated/cluster-objects.h>
#include <app/AppConfig.h>
#include <app/InteractionModelEngine.h>
#include <app/data-model/NullObject.h>
#include <app/tests/AppTestContext.h>
#include <controller/InvokeInteraction.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/ErrorStr.h>
#include <lib/core/TLV.h>
#include <lib/core/TLVUtilities.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/tests/MessagingContext.h>
#include <protocols/interaction_model/Constants.h>
#include <protocols/interaction_model/StatusCode.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::DataModelTests;
using namespace chip::Protocols;
namespace {
const chip::Test::MockNodeConfig & TestMockNodeConfig()
{
using namespace chip::app;
using namespace chip::Test;
using namespace chip::app::Clusters::Globals::Attributes;
// clang-format off
static const MockNodeConfig config({
MockEndpointConfig(kTestEndpointId, {
MockClusterConfig(Clusters::UnitTesting::Id, {
ClusterRevision::Id, FeatureMap::Id,
},
{}, // events
{
Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Id,
}, // accepted commands
{} // generated commands
),
}),
});
// clang-format on
return config;
}
class TestCommands : public chip::Test::AppContext
{
public:
void SetUp() override
{
AppContext::SetUp();
mOldProvider = InteractionModelEngine::GetInstance()->SetDataModelProvider(&CustomDataModel::Instance());
chip::Test::SetMockNodeConfig(TestMockNodeConfig());
}
void TearDown() override
{
chip::Test::ResetMockNodeConfig();
InteractionModelEngine::GetInstance()->SetDataModelProvider(mOldProvider);
AppContext::TearDown();
}
protected:
chip::app::DataModel::Provider * mOldProvider = nullptr;
};
TEST_F(TestCommands, TestDataResponse)
{
// We want to send a TestSimpleArgumentRequest::Type, but get a
// TestStructArrayArgumentResponse in return, so need to shadow the actual
// ResponseType that TestSimpleArgumentRequest has.
struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type
{
using ResponseType = Clusters::UnitTesting::Commands::TestStructArrayArgumentResponse::DecodableType;
};
FakeRequest request;
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessWasCalled = false;
bool onFailureWasCalled = false;
request.arg1 = 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 onSuccessCb = [&onSuccessWasCalled](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus,
const auto & dataResponse) {
uint8_t i = 0;
auto iter = dataResponse.arg1.begin();
while (iter.Next())
{
auto & item = iter.GetValue();
EXPECT_EQ(item.a, i);
EXPECT_FALSE(item.b);
EXPECT_EQ(item.c.a, i);
EXPECT_TRUE(item.c.b);
i++;
}
EXPECT_EQ(iter.GetStatus(), CHIP_NO_ERROR);
EXPECT_TRUE(dataResponse.arg6);
onSuccessWasCalled = 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 = [&onFailureWasCalled](CHIP_ERROR aError) { onFailureWasCalled = true; };
ScopedChange directive(gCommandResponseDirective, CommandResponseDirective::kSendDataResponse);
chip::Controller::InvokeCommandRequest(&GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(onSuccessWasCalled);
EXPECT_FALSE(onFailureWasCalled);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestCommands, TestSuccessNoDataResponse)
{
struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type
{
using ResponseType = DataModel::NullObjectType;
};
FakeRequest request;
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessWasCalled = false;
bool onFailureWasCalled = false;
bool statusCheck = false;
request.arg1 = 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 onSuccessCb = [&onSuccessWasCalled, &statusCheck](const app::ConcreteCommandPath & commandPath,
const app::StatusIB & aStatus, const auto & dataResponse) {
statusCheck = (aStatus.mStatus == Protocols::InteractionModel::Status::Success);
onSuccessWasCalled = 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 = [&onFailureWasCalled](CHIP_ERROR aError) { onFailureWasCalled = true; };
ScopedChange directive(gCommandResponseDirective, CommandResponseDirective::kSendSuccessStatusCode);
chip::Controller::InvokeCommandRequest(&GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(onSuccessWasCalled && !onFailureWasCalled && statusCheck);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestCommands, TestMultipleSuccessNoDataResponses)
{
struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type
{
using ResponseType = DataModel::NullObjectType;
};
FakeRequest request;
auto sessionHandle = GetSessionBobToAlice();
size_t successCalls = 0;
size_t failureCalls = 0;
bool statusCheck = false;
request.arg1 = 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 onSuccessCb = [&successCalls, &statusCheck](const ConcreteCommandPath & commandPath, const StatusIB & aStatus,
const auto & dataResponse) {
statusCheck = (aStatus.mStatus == Protocols::InteractionModel::Status::Success);
++successCalls;
};
// 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 = [&failureCalls](CHIP_ERROR aError) { ++failureCalls; };
ScopedChange directive(gCommandResponseDirective, CommandResponseDirective::kSendMultipleSuccessStatusCodes);
Controller::InvokeCommandRequest(&GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(successCalls == 1 && statusCheck);
EXPECT_EQ(failureCalls, 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestCommands, TestAsyncResponse)
{
struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type
{
using ResponseType = DataModel::NullObjectType;
};
FakeRequest request;
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessWasCalled = false;
bool onFailureWasCalled = false;
bool statusCheck = false;
request.arg1 = 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 onSuccessCb = [&onSuccessWasCalled, &statusCheck](const app::ConcreteCommandPath & commandPath,
const app::StatusIB & aStatus, const auto & dataResponse) {
statusCheck = (aStatus.mStatus == Protocols::InteractionModel::Status::Success);
onSuccessWasCalled = 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 = [&onFailureWasCalled](CHIP_ERROR aError) { onFailureWasCalled = true; };
ScopedChange directive(gCommandResponseDirective, CommandResponseDirective::kAsync);
chip::Controller::InvokeCommandRequest(&GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(!onSuccessWasCalled && !onFailureWasCalled && !statusCheck);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 2u);
CommandHandler * commandHandle = gAsyncCommandHandle.Get();
ASSERT_NE(commandHandle, nullptr);
commandHandle->AddStatus(ConcreteCommandPath(kTestEndpointId, request.GetClusterId(), request.GetCommandId()),
Protocols::InteractionModel::Status::Success);
gAsyncCommandHandle.Release();
DrainAndServiceIO();
EXPECT_TRUE(onSuccessWasCalled && !onFailureWasCalled && statusCheck);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestCommands, TestFailure)
{
Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type request;
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessWasCalled = false;
bool onFailureWasCalled = false;
bool statusCheck = false;
request.arg1 = 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 onSuccessCb = [&onSuccessWasCalled](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus,
const auto & dataResponse) { onSuccessWasCalled = 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 = [&onFailureWasCalled, &statusCheck](CHIP_ERROR aError) {
statusCheck = aError.IsIMStatus() && app::StatusIB(aError).mStatus == Protocols::InteractionModel::Status::Failure;
onFailureWasCalled = true;
};
ScopedChange directive(gCommandResponseDirective, CommandResponseDirective::kSendError);
chip::Controller::InvokeCommandRequest(&GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(!onSuccessWasCalled && onFailureWasCalled && statusCheck);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestCommands, TestMultipleFailures)
{
struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type
{
using ResponseType = DataModel::NullObjectType;
};
FakeRequest request;
auto sessionHandle = GetSessionBobToAlice();
size_t successCalls = 0;
size_t failureCalls = 0;
bool statusCheck = false;
request.arg1 = 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 onSuccessCb = [&successCalls](const ConcreteCommandPath & commandPath, const StatusIB & aStatus,
const auto & dataResponse) { ++successCalls; };
// 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 = [&failureCalls, &statusCheck](CHIP_ERROR aError) {
statusCheck = aError.IsIMStatus() && StatusIB(aError).mStatus == Protocols::InteractionModel::Status::Failure;
++failureCalls;
};
ScopedChange directive(gCommandResponseDirective, CommandResponseDirective::kSendMultipleErrors);
Controller::InvokeCommandRequest(&GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb, onFailureCb);
DrainAndServiceIO();
EXPECT_EQ(successCalls, 0u);
EXPECT_TRUE(failureCalls == 1 && statusCheck);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestCommands, TestSuccessNoDataResponseWithClusterStatus)
{
struct FakeRequest : public Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type
{
using ResponseType = DataModel::NullObjectType;
};
FakeRequest request;
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessWasCalled = false;
bool onFailureWasCalled = false;
bool statusCheck = false;
request.arg1 = 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 onSuccessCb = [&onSuccessWasCalled, &statusCheck](const app::ConcreteCommandPath & commandPath,
const app::StatusIB & aStatus, const auto & dataResponse) {
statusCheck = (aStatus.mStatus == Protocols::InteractionModel::Status::Success &&
aStatus.mClusterStatus.Value() == kTestSuccessClusterStatus);
onSuccessWasCalled = 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 = [&onFailureWasCalled](CHIP_ERROR aError) { onFailureWasCalled = true; };
ScopedChange directive(gCommandResponseDirective, CommandResponseDirective::kSendSuccessStatusCodeWithClusterStatus);
chip::Controller::InvokeCommandRequest(&GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(onSuccessWasCalled && !onFailureWasCalled && statusCheck);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestCommands, TestFailureWithClusterStatus)
{
Clusters::UnitTesting::Commands::TestSimpleArgumentRequest::Type request;
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessWasCalled = false;
bool onFailureWasCalled = false;
bool statusCheck = false;
request.arg1 = 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 onSuccessCb = [&onSuccessWasCalled](const app::ConcreteCommandPath & commandPath, const app::StatusIB & aStatus,
const auto & dataResponse) { onSuccessWasCalled = 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 = [&onFailureWasCalled, &statusCheck](CHIP_ERROR aError) {
statusCheck = aError.IsIMStatus();
if (statusCheck)
{
app::StatusIB status(aError);
statusCheck = (status.mStatus == Protocols::InteractionModel::Status::Failure &&
status.mClusterStatus == MakeOptional(kTestFailureClusterStatus));
}
onFailureWasCalled = true;
};
ScopedChange directive(gCommandResponseDirective, CommandResponseDirective::kSendErrorWithClusterStatus);
chip::Controller::InvokeCommandRequest(&GetExchangeManager(), sessionHandle, kTestEndpointId, request, onSuccessCb,
onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(!onSuccessWasCalled && onFailureWasCalled && statusCheck);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
} // namespace