blob: a1c1ad2e63c77ee06142c5513ed2e77d14b434a9 [file] [log] [blame]
/*
* Copyright (c) 2025 Project CHIP Authors
*
* 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.
*/
#pragma once
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/MessageDef/CommandDataIB.h>
#include <app/data-model-provider/ActionReturnStatus.h>
#include <app/data-model-provider/OperationTypes.h>
#include <app/data-model/NullObject.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/TLVReader.h>
#include <lib/core/TLVWriter.h>
#include <lib/support/CodeUtils.h>
#include <messaging/ExchangeContext.h>
#include <memory>
#include <vector>
namespace chip {
namespace Testing {
constexpr FabricIndex kTestFabricIndex = static_cast<FabricIndex>(151);
// Mock class that simulates CommandHandler behavior for unit testing, allowing capture and
// verification of responses and statuses without real network interactions.
class MockCommandHandler : public app::CommandHandler
{
public:
struct ResponseRecord
{
CommandId commandId;
System::PacketBufferHandle encodedData;
app::ConcreteCommandPath path;
};
struct StatusRecord
{
app::ConcreteCommandPath path;
Protocols::InteractionModel::ClusterStatusCode status;
const char * context;
};
MockCommandHandler() = default;
// Copying is disallowed because ResponseRecord contains PacketBufferHandle,
// which is move-only and represents unique ownership of encoded TLV data.
//
// Move is allowed because this mock does NOT maintain any self-referencing,
// intrusive, or address-stable state (unlike the real CommandHandler / Handle
// system), so relocating the object is safe.
MockCommandHandler(const MockCommandHandler &) = delete;
MockCommandHandler & operator=(const MockCommandHandler &) = delete;
MockCommandHandler(MockCommandHandler &&) = default;
MockCommandHandler & operator=(MockCommandHandler &&) = default;
~MockCommandHandler() override = default;
CHIP_ERROR FallibleAddStatus(const app::ConcreteCommandPath & aRequestCommandPath,
const Protocols::InteractionModel::ClusterStatusCode & aStatus,
const char * context = nullptr) override;
void AddStatus(const app::ConcreteCommandPath & aRequestCommandPath,
const Protocols::InteractionModel::ClusterStatusCode & aStatus, const char * context = nullptr) override;
FabricIndex GetAccessingFabricIndex() const override { return mFabricIndex; }
// Encodes and stores response data, returning error if encoding fails (fallible version for robust test handling).
CHIP_ERROR AddResponseData(const app::ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
const app::DataModel::EncodableToTLV & aEncodable) override;
// Encodes and stores response data, without error return (non-fallible version that assumes successful encoding in tests).
void AddResponse(const app::ConcreteCommandPath & aRequestCommandPath, CommandId aResponseCommandId,
const app::DataModel::EncodableToTLV & aEncodable) override;
bool IsTimedInvoke() const override { return false; }
void FlushAcksRightAwayOnSlowCommand() override {}
Access::SubjectDescriptor GetSubjectDescriptor() const override { return Access::SubjectDescriptor{}; }
Messaging::ExchangeContext * GetExchangeContext() const override { return nullptr; }
// Helper methods to extract response data
bool HasResponse() const { return !mResponses.empty(); }
size_t GetResponseCount() const { return mResponses.size(); }
// Methods for working with single response (first in array)
CommandId GetResponseCommandId() const { return mResponses.empty() ? 0 : mResponses[0].commandId; }
const ResponseRecord & GetResponse() const { return mResponses[0]; }
// Methods for working with all responses
const std::vector<ResponseRecord> & GetResponses() const { return mResponses; }
const ResponseRecord & GetResponse(size_t index) const { return mResponses[index]; }
void ClearResponses() { mResponses.clear(); }
// Helper methods to access stored statuses
bool HasStatus() const { return !mStatuses.empty(); }
const std::vector<StatusRecord> & GetStatuses() const { return mStatuses; }
const StatusRecord & GetLastStatus() const { return mStatuses.back(); }
void ClearStatuses() { mStatuses.clear(); }
// Get a TLV reader positioned at the response data fields (first response)
CHIP_ERROR GetResponseReader(TLV::TLVReader & reader) const;
// Get a TLV reader positioned at the response data fields (specific response)
CHIP_ERROR GetResponseReader(TLV::TLVReader & reader, size_t index) const;
// Decode response into a specific DecodableType (first response)
template <typename ResponseType>
CHIP_ERROR DecodeResponse(ResponseType & response) const
{
TLV::TLVReader reader;
ReturnErrorOnFailure(GetResponseReader(reader));
return response.Decode(reader);
}
// Decode specific response into a specific DecodableType
template <typename ResponseType>
CHIP_ERROR DecodeResponse(ResponseType & response, size_t index) const
{
TLV::TLVReader reader;
ReturnErrorOnFailure(GetResponseReader(reader, index));
return response.Decode(reader);
}
// Configuration methods
void SetFabricIndex(FabricIndex index) { mFabricIndex = index; }
private:
std::vector<ResponseRecord> mResponses;
std::vector<StatusRecord> mStatuses;
FabricIndex mFabricIndex = kTestFabricIndex; // Default to a clearly test-only fabric index.
};
} // namespace Testing
} // namespace chip