blob: 218832702b230f88edcefc1dcd2485a07240bbc4 [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.
*/
#include <lib/core/StringBuilderAdapters.h>
#include <pw_unit_test/framework.h>
#include "DataModelFixtures.h"
#include "app-common/zap-generated/ids/Clusters.h"
#include <app-common/zap-generated/cluster-objects.h>
#include <app/AttributeValueDecoder.h>
#include <app/InteractionModelEngine.h>
#include <app/WriteClient.h>
#include <app/tests/AppTestContext.h>
#include <controller/WriteInteraction.h>
#include <lib/core/ErrorStr.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/tests/MessagingContext.h>
#include <protocols/interaction_model/Constants.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::UnitTesting;
using namespace chip::app::DataModelTests;
using namespace chip::Protocols;
namespace {
class SingleWriteCallback : public WriteClient::Callback
{
public:
explicit SingleWriteCallback(ConcreteAttributePath path) : mPath(path) {}
void OnResponse(const WriteClient * apWriteClient, const ConcreteDataAttributePath & aPath, StatusIB attributeStatus) override
{
if (aPath.MatchesConcreteAttributePath(mPath))
{
mPathWasReponded = true;
mPathStatus = attributeStatus;
}
}
void OnError(const WriteClient * apWriteClient, CHIP_ERROR aError) override
{
(void) apWriteClient;
mLastChipError = aError;
}
void OnDone(WriteClient * apWriteClient) override
{
(void) apWriteClient;
mOnDoneCalled = true;
}
bool WasDone() const { return mOnDoneCalled; }
bool PathWasResponded() const { return mOnDoneCalled; }
CHIP_ERROR GetLastChipError() const { return mLastChipError; }
StatusIB GetPathStatus() const { return mPathStatus; }
private:
ConcreteAttributePath mPath;
bool mOnDoneCalled = false;
CHIP_ERROR mLastChipError = CHIP_NO_ERROR;
bool mPathWasReponded = false;
StatusIB mPathStatus;
};
class TestWrite : public chip::Test::AppContext
{
public:
void SetUp() override
{
chip::Test::AppContext::SetUp();
mOldProvider = InteractionModelEngine::GetInstance()->SetDataModelProvider(&CustomDataModel::Instance());
}
// Performs teardown for each individual test in the test suite
void TearDown() override
{
InteractionModelEngine::GetInstance()->SetDataModelProvider(mOldProvider);
chip::Test::AppContext::TearDown();
}
void ResetCallback() { mSingleWriteCallback.reset(); }
void PrepareWriteCallback(ConcreteAttributePath path) { mSingleWriteCallback = std::make_unique<SingleWriteCallback>(path); }
SingleWriteCallback * GetWriteCallback() { return mSingleWriteCallback.get(); }
protected:
std::unique_ptr<SingleWriteCallback> mSingleWriteCallback;
chip::app::DataModel::Provider * mOldProvider = nullptr;
};
TEST_F(TestWrite, TestDataResponse)
{
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4];
Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value;
value = valueBuf;
uint8_t i = 0;
for (auto & item : valueBuf)
{
item.member1 = i;
i++;
}
ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendAttributeSuccess);
// 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 ConcreteAttributePath & attributePath) { 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 ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
onFailureCbInvoked = true;
};
chip::Controller::WriteAttribute<Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo>(
sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(onSuccessCbInvoked);
EXPECT_FALSE(onFailureCbInvoked);
EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestWrite, TestDataResponseWithAcceptedDataVersion)
{
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4];
Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value;
value = valueBuf;
uint8_t i = 0;
for (auto & item : valueBuf)
{
item.member1 = i;
i++;
}
ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendAttributeSuccess);
// 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) { 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, CHIP_ERROR aError) {
onFailureCbInvoked = true;
};
chip::Optional<chip::DataVersion> dataVersion;
dataVersion.SetValue(kAcceptedDataVersion);
chip::Controller::WriteAttribute<Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo>(
sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb, nullptr, dataVersion);
DrainAndServiceIO();
EXPECT_TRUE(onSuccessCbInvoked && !onFailureCbInvoked);
EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestWrite, TestDataResponseWithRejectedDataVersion)
{
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
Clusters::UnitTesting::Structs::TestListStructOctet::Type valueBuf[4];
Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo::Type value;
value = valueBuf;
uint8_t i = 0;
for (auto & item : valueBuf)
{
item.member1 = i;
i++;
}
ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendAttributeSuccess);
// 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) { 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, CHIP_ERROR aError) {
onFailureCbInvoked = true;
};
chip::Optional<chip::DataVersion> dataVersion(kRejectedDataVersion);
chip::Controller::WriteAttribute<Clusters::UnitTesting::Attributes::ListStructOctetString::TypeInfo>(
sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb, nullptr, dataVersion);
DrainAndServiceIO();
EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked);
EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestWrite, TestAttributeError)
{
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
Attributes::ListStructOctetString::TypeInfo::Type value;
Structs::TestListStructOctet::Type valueBuf[4];
value = valueBuf;
uint8_t i = 0;
for (auto & item : valueBuf)
{
item.member1 = i;
i++;
}
ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendAttributeError);
// 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 ConcreteAttributePath & attributePath) { 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 ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
EXPECT_TRUE(attributePath != nullptr);
onFailureCbInvoked = true;
};
Controller::WriteAttribute<Attributes::ListStructOctetString::TypeInfo>(sessionHandle, kTestEndpointId, value, onSuccessCb,
onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked);
EXPECT_EQ(InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestWrite, TestFabricScopedAttributeWithoutFabricIndex)
{
auto sessionHandle = GetSessionBobToAlice();
bool onSuccessCbInvoked = false, onFailureCbInvoked = false;
Clusters::UnitTesting::Structs::TestFabricScoped::Type valueBuf[4];
Clusters::UnitTesting::Attributes::ListFabricScoped::TypeInfo::Type value;
value = valueBuf;
uint8_t i = 0;
for (auto & item : valueBuf)
{
item.fabricIndex = i;
i++;
}
// 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 ConcreteAttributePath & attributePath) { 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 ConcreteAttributePath * attributePath, CHIP_ERROR aError) {
EXPECT_EQ(aError, CHIP_IM_GLOBAL_STATUS(UnsupportedAccess));
onFailureCbInvoked = true;
};
chip::Controller::WriteAttribute<Clusters::UnitTesting::Attributes::ListFabricScoped::TypeInfo>(
sessionHandle, kTestEndpointId, value, onSuccessCb, onFailureCb);
DrainAndServiceIO();
EXPECT_TRUE(!onSuccessCbInvoked && onFailureCbInvoked);
EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestWrite, TestMultipleSuccessResponses)
{
auto sessionHandle = GetSessionBobToAlice();
size_t successCalls = 0;
size_t failureCalls = 0;
ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendMultipleSuccess);
// 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 ConcreteAttributePath & attributePath) { ++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](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { ++failureCalls; };
chip::Controller::WriteAttribute<Clusters::UnitTesting::Attributes::Boolean::TypeInfo>(sessionHandle, kTestEndpointId, true,
onSuccessCb, onFailureCb);
DrainAndServiceIO();
EXPECT_EQ(successCalls, 1u);
EXPECT_EQ(failureCalls, 0u);
EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestWrite, TestMultipleFailureResponses)
{
auto sessionHandle = GetSessionBobToAlice();
size_t successCalls = 0;
size_t failureCalls = 0;
ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendMultipleErrors);
// 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 ConcreteAttributePath & attributePath) { ++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](const ConcreteAttributePath * attributePath, CHIP_ERROR aError) { ++failureCalls; };
chip::Controller::WriteAttribute<Clusters::UnitTesting::Attributes::Boolean::TypeInfo>(sessionHandle, kTestEndpointId, true,
onSuccessCb, onFailureCb);
DrainAndServiceIO();
EXPECT_EQ(successCalls, 0u);
EXPECT_EQ(failureCalls, 1u);
EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
TEST_F(TestWrite, TestWriteClusterSpecificStatuses)
{
auto sessionHandle = GetSessionBobToAlice();
// Cluster-specific success code case
{
ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendClusterSpecificSuccess);
this->ResetCallback();
this->PrepareWriteCallback(
ConcreteAttributePath{ kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int8u::Id });
SingleWriteCallback * writeCb = this->GetWriteCallback();
WriteClient writeClient(&GetExchangeManager(), this->GetWriteCallback(), Optional<uint16_t>::Missing());
AttributePathParams attributePath{ kTestEndpointId, Clusters::UnitTesting::Id,
Clusters::UnitTesting::Attributes::Int8u::Id };
constexpr uint8_t attributeValue = 1u;
ASSERT_EQ(writeClient.EncodeAttribute(attributePath, attributeValue), CHIP_NO_ERROR);
ASSERT_EQ(writeClient.SendWriteRequest(sessionHandle), CHIP_NO_ERROR);
DrainAndServiceIO();
EXPECT_TRUE(writeCb->WasDone());
EXPECT_TRUE(writeCb->PathWasResponded());
EXPECT_EQ(writeCb->GetLastChipError(), CHIP_NO_ERROR);
StatusIB pathStatus = writeCb->GetPathStatus();
EXPECT_EQ(pathStatus.mStatus, Protocols::InteractionModel::Status::Success);
ASSERT_TRUE(pathStatus.mClusterStatus.HasValue());
EXPECT_EQ(pathStatus.mClusterStatus.Value(), kExampleClusterSpecificSuccess);
EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
// Cluster-specific failure code case
{
ScopedChange directive(gWriteResponseDirective, WriteResponseDirective::kSendClusterSpecificFailure);
this->ResetCallback();
this->PrepareWriteCallback(
ConcreteAttributePath{ kTestEndpointId, Clusters::UnitTesting::Id, Clusters::UnitTesting::Attributes::Int8u::Id });
SingleWriteCallback * writeCb = this->GetWriteCallback();
WriteClient writeClient(&GetExchangeManager(), this->GetWriteCallback(), Optional<uint16_t>::Missing());
AttributePathParams attributePath{ kTestEndpointId, Clusters::UnitTesting::Id,
Clusters::UnitTesting::Attributes::Int8u::Id };
constexpr uint8_t attributeValue = 2u;
ASSERT_EQ(writeClient.EncodeAttribute(attributePath, attributeValue), CHIP_NO_ERROR);
ASSERT_EQ(writeClient.SendWriteRequest(sessionHandle), CHIP_NO_ERROR);
DrainAndServiceIO();
EXPECT_TRUE(writeCb->WasDone());
EXPECT_TRUE(writeCb->PathWasResponded());
EXPECT_EQ(writeCb->GetLastChipError(), CHIP_NO_ERROR);
StatusIB pathStatus = writeCb->GetPathStatus();
EXPECT_EQ(pathStatus.mStatus, Protocols::InteractionModel::Status::Failure);
ASSERT_TRUE(pathStatus.mClusterStatus.HasValue());
EXPECT_EQ(pathStatus.mClusterStatus.Value(), kExampleClusterSpecificFailure);
EXPECT_EQ(chip::app::InteractionModelEngine::GetInstance()->GetNumActiveWriteHandlers(), 0u);
EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u);
}
}
} // namespace