// Copyright 2016-2023 Bloomberg Finance L.P.
// SPDX-License-Identifier: Apache-2.0
//
// 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.

// bmqa_mocksession.h                                                 -*-C++-*-
#ifndef INCLUDED_BMQA_MOCKSESSION
#define INCLUDED_BMQA_MOCKSESSION

//@PURPOSE: Provide a mock session, implementing 'bmqa::AbstractSession'.
//
//@CLASSES:
//  bmqa::MockSession:     mechanism to mock a 'bmqa::Session'
//  bmqa::MockSessionUtil: utility methods to create 'bmqa' events
//
//@DESCRIPTION: This component provides a mechanism implementing the
// 'bmqa::AbstractSession' protocol, for mocking a 'bmqa::Session' and can be
// used to write a test for an application that uses BMQ.  The
// 'bmqa::MockSession' provides all the methods that 'Session' provides, with
// added methods to specify return codes and emitted events and expected calls.
// This can be used to test BlazingMQ application code without a connection to
// the broker.  'bmqa::MockSessionUtil' is a utility namespace providing useful
// methods to build 'bmqa::Event' objects that are typically only emitted from
// the broker.
//
// The following documentation elucidates the API that this component provides
// and some simple use cases to get you started.
//
//
///Disclaimer
///----------
// THIS COMPONENT SHOULD ONLY BE USED IN TEST DRIVERS.  IT WILL NOT WORK WITH
// PRODUCTION CODE.
//
//
///Usable Components
///-----------------
//: o !BMQA_EXPECT_CALL!: Macro to specify an expected call to a
//:                       'bmqa::MockSession' object. This macro is used to
//:                       specify which is the next expected call on the
//:                       'bmqa::MockSession'.  If an incorrect call is invoked
//:                       or incorrect parameters are used, an assert will be
//:                       invoked.
//
//: o !returning!       : Specify a return value for the expected call.  This
//:                       is the value that will be returned when the method on
//:                       'bmqa::MockSession' is invoked.
//
//: o !emitting!        : Specify an event to be 'emitted' when the expected
//:                       call is invoked.  The events specified are enqueued
//:                       to the internal event queue and are delivered to the
//:                       application when 'emitEvent' is invoked.
//
///Static Helper Methods
///---------------------
//: o !createAckEvent!            : Create an acknowledgment message event for
//:                                 messages posted to BMQ.
//
//: o !createPushEvent!           : Create a push message event for messages to
//:                                 be consumed from BMQ.
//
//: o !createOpenQueueStatus!     : Create an openQueue result (relating to an
//:                                 async open queue operation)
//
//: o !createConfigureQueueStatus!: Create a configureQueue result (relating to
//:                                 an async configure queue operation)
//
//: o !createCloseQueueStatus!    : Create a closeQueue result (relating to an
//:                                 async close queue operation)
//
//: o !createSessionEvent!        : Create a specified type of session event
//:                                 except for events related to open, close
//:                                 and configure queue.
//
// The static event builder specified above are typically built inside the
// broker but are now available to be built in the SDK.  The expected use of
// such events is to build them and specify them to either the
// 'BMQA_EXPECT_CALL' macro in the 'emitting' parameter, or enqueued to the
// 'bmqa::MockSession' directly through the 'enqueueEvent' method.  They can
// then be emitted by invoking the 'emitEvent' method, which in turn would be
// processed through the application-provided 'bmqa::SessionEventHandler'.
//
///Additional Note
///---------------
// 'MockSession' does not check if methods have been invoked in the correct
// order.  The user is responsible for ensuring that the methods are invoked
// and events enqueued in the correct order.
//
// The following methods do not emit events:
//: o 'getQueueId'
//: o 'loadMessageEventBuilder'
//: o 'loadConfirmEventBuilder'
//: o 'loadMessageProperties'
//: o 'confirmMessage'
//: o 'confirmMessages'
//
// Calls to the following methods do not require an expect:
//: o 'getQueueId'
//: o 'loadMessageEventBuilder'
//: o 'loadConfirmEventBuilder'
//: o 'loadMessageProperties'
//
//
///Creating a mock session in asynchronous mode
///--------------------------------------------
// The 'MockSession' is created in asynchronous mode when a
// 'SessionEventHandler' is provided to it.  If it is not provided a handler,
// the 'MockSession' is started in synchronous mode, requiring the application
// to call 'nextEvent' to access enqueued events.  A sample handler could look
// like this:
//
//..
//  class MyEventHandler : public bmqa::SessionEventHandler {
//
//    private:
//      // DATA
//      bsl::deque<bmqa::SessionEvent>    d_sessionEventsQueue;
//      bsl::deque<bmqa::MessageEvents>   d_messageEventsQueue;
//      bsl::deque<bmqa::OpenQueueStatus> d_openQueueResultsQueue;
//      ...
//
//    public:
//      // MANIPULATORS
//      virtual void onSessionEvent(const bmqa::SessionEvent& event)
//      {
//          bsl::cout << "Received session event " << event << "\n";
//          // some business logic, typically a switch case on
//          // 'bmqt::SessionEventType'
//          d_sessionEventsQueue.push_back(event);
//      }
//
//      virtual void onMessageEvent(const bmqa::MessageEvent& event)
//      {
//          bsl::cout << "Received message event " << event << "\n";
//          // some business logic, typically a switch case on
//          // 'bmqt::MessageEventType'
//          d_messageEventsQueue.push_back(event);
//      }
//
//      void onOpenQueueStatus(const bmqa::OpenQueueStatus& result)
//      {
//          bsl::cout << "Received open queue result: " << result << "\n";
//          // Some business logic
//          d_openQueueResultsQueue.push_back(result);
//      }
//      ...
//
//      bmqa::SessionEvent popSessionEvent()
//      {
//          BSLS_ASSERT(d_sessionEventsQueue.size() > 0);
//          bmqa::SessionEvent ret(d_receivedSessionEvents.front());
//          d_receivedSessionEvents.pop_front();
//          return ret;
//      }
//
//      bmqa::MessageEvent popMessageEvent()
//      {
//          BSLS_ASSERT(d_messageEventsSize.size() > 0);
//          bmqa::MessageEvent ret(d_receivedMessageEvents.front());
//          d_receivedMessageEvents.erase(d_receivedMessageEvents.begin());
//          return ret;
//      }
//
//      bmqa::OpenQueueStatus popOpenQueueStatus()
//      {
//          BSLS_ASSERT(d_openQueueResultsQueue.size() > 0);
//          bmqa::OpenQueueStatus ret(d_openQueueResultsQueue.front());
//          d_openQueueResultsQueue.erase(d_openQueueResultsQueue.begin());
//          return ret;
//      }
//      ...
//  };
//..
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1
///- - - - -
// The folowing example shows a simple producer in asynchronous mode, which
// will start the session, open a queue, post a message to the queue, generate
// an ack for that message and finally stop the session (skipping over close
// queue because it is analogous to opening a queue).  In theory, you can use
// 'emitting' on the 'BMQA_EXPECT_CALL' macro and 'enqueueEvent'
// interchangeably, but in practice it is important to note that events from
// the broker are generated asynchronously, which means that they are not
// emitted as you call the method.  You can control emission of events,
// however, by delaying the call to 'emitEvent'.
//
// NOTE: As with 'bmqa::Session', calling 'nextEvent' is meaningless in
//       asynchronous mode.
//
//..
//  void unitTest()
//  {
//       // Create an event handler
//       EventHandler eventHandler(d_allocator_p);
//
//       // The following static initializer method calls all the appropriate
//       // static initializers of the underlying components needed for the
//       // 'MockSession'.  The constructor of 'MockSession' will call it in
//       // any case but if events need to be built outside the scope of the
//       // creation of 'MockSession' you will need to explicitly invoke this
//       // static initializer method.
//       // bmqa::MockSession::initialize(s_allocator_p);
//
//       bslma::ManagedPtr<bmqa::SessionEventHandler> handlerMp;
//       handlerMp.load(&eventHandler, 0, bslma::ManagedPtrUtil::noOpDeleter);
//
//       bmqa::MockSession mockSession(handlerMp,
//                                     bmqt::SessionOptions(d_allocator_p),
//                                     d_allocator_p);
//
//       bmqa::QueueId       queueId(bmqt::CorrelationId(1), d_allocator_p);
//       bmqt::CorrelationId corrId(1);
//
//       // Expect a call to start and the call emits an 'e_CONNECTED' event.
//       BMQA_EXPECT_CALL(mockSession, startAsync())
//           .returning(0)
//           .emitting(bmqa::MockSessionUtil::createSessionEvent(
//                         bmqt::SessionEventType::e_CONNECTED,
//                         0,   // statusCode
//                         "",  // errorDescription
//                         d_allocator_p));
//
//       // Make a call to startAsync and emit the event that is enqueued from
//       // that call.
//       ASSERT_EQ(mockSession.startAsync(), 0);
//
//       // Emit our enqueued event.  This fully sets up the session which is
//       // now ready to use.  Typically you would have some business logic on
//       // 'e_CONNECTED' that makes your application ready to use.
//       ASSERT_EQ(mockSession.emitEvent(), true);
//
//       // Our event handler internally just stores the event emitted, so pop
//       // it out and examine.
//       bmqa::SessionEvent startEvent(eventHandler.popSessionEvent());
//
//       ASSERT_EQ(startEvent.type(), bmqt::SessionEventType::e_CONNECTED);
//       ASSERT_EQ(startEvent.statusCode(), 0);
//
//       // Create the uri to your queue as you would in your application.
//       const bmqt::Uri uri("bmq://my.domain/queue");
//
//       // Initialize the queue flags for a producer with acks enabled
//       bsls::Types::Uint64 flags = 0;
//       bmqt::QueueFlagsUtil::setWriter(&flags);
//       bmqt::QueueFlagsUtil::setAck(&flags);
//
//       // We use the macro to expect a call to 'openQueueAsync', binding the
//       // 'uri' and 'queueId' objects as well as the 'flags' that we created.
//       bmqa::MockSession::OpenQueueCallback openQueueCallback =
//           bdlf::BindUtil::bind(&EventHandler::onOpenQueueStatus,
//                                &eventHandler,
//                                bdlf::PlaceHolders::_1); // result
//
//       BMQA_EXPECT_CALL(mockSession,
//                        openQueueAsync(uri1,
//                                       flags,
//                                       openQueueCallback));
//       BMQA_EXPECT_CALL(mockSession,
//                        openQueueAsync(uri, flags, openQueueCallback));
//
//       // Now that we have set our expectations we can try to open the queue.
//       mockSession.openQueueAsync(uri1, flags, openQueueCallback);
//
//       // Since the application may not have direct access to the queue, we
//       // need to get the 'queueId' from the session.  We can then bind this
//       // retrieved 'queueId' to the 'e_QUEUE_OPEN_RESULT' session event and
//       // enqueue it to the 'MockSession'.
//       // Note: You can only get the 'queueId' after 'openQueue' or
//       //       'openQueueAsync' has been invoked on the session.
//       bmqa::QueueId         queueId1(corrId1);
//       bmqa::OpenQueueStatus openQueueResult =
//           bmqa::MockSessionUtil::createOpenQueueStatus(
//                       queueId1,
//                       bmqt::OpenQueueResult::e_TIMEOUT,  // statusCode
//                       "Local Timeout",                   // errorDescription
//                       d_allocator_p);
//       mockSession.enqueueEvent(openQueueResult);
//
//       // We just enqueued a 'bmqa::OpenQueueStatus' to be emitted.  We can
//       // emit it using 'emitEvent'.
//       ASSERT_EQ(mockSession.emitEvent(), true);
//
//       //  Pop out this event from the handler and examine it.
//       bmqa::OpenQueueStatus result = eventHandler.popOpenQueueStatus();
//       ASSERT_EQ(result, openQueueResult);
//
//       // On emission of 'bmqa::OpenQueueStatus', the queue is fully open and
//       // we can now post to it.
//       bmqa::MessageEventBuilder builder;
//       mockSession.loadMessageEventBuilder(&builder);
//
//       BMQA_EXPECT_CALL(mockSession, post(builder.messageEvent()))
//           .returning(0);
//
//       // Use the builder to build a mesage event and pack it for the queue
//       // that has been opened.  If you try to pack the message for an
//       // invalid or closed queue, packing the message will fail. This has
//       // been elided for brevity.
//
//       // Now that the event has been built we can 'post' it to BMQ.
//       ASSERT_EQ(mockSession.post(builder.messageEvent()), 0);
//
//       // Simply creating a blob buffer factory on the stack to be used by
//       // 'createAckEvent'.  Typically you would have one for the component.
//       bdlbb::PooledBlobBufferFactory bufferFactory(4 * 1024, d_allocator_p);
//
//       // The method 'createAckEvent' takes a vector of 'AckParams' to
//       // specify multiple acks per event, but here we are only acknowledging
//       // 1 message.  Specify a positive ack with 'e_SUCCESS' here but you
//       // can specify any from 'bmqt::AckResult::Enum'.
//       bsl::vector<bmqa::MockSessionUtil::AckParams> acks(d_allocator_p);
//       acks.emplace_back(bmqt::AckResult::e_SUCCESS,
//                         bmqt::CorrelationId(1),
//                         bmqt::MessageGUID(), // Real GUID needed if you want
//                                              // to record ack messages.
//                         bmqa::QueueId(1));
//
//       // Enqueuing ack event to be emitted.  We use the helper function
//       // 'createAckEvent' to generate this event.
//       mockSession.enqueueEvent(bmqa::MockSessionUtil::createAckEvent(
//                                                             acks,
//                                                             &bufferFactory,
//                                                             d_allocator_p));
//
//       // Emit the enqueued ack event.
//       ASSERT_EQ(mockSession.emitEvent(), true);
//
//       // As we did earlier, pop it out and examine.
//       bmqa::MessageEvent ackEvent(eventHandler.popMessageEvent());
//       ASSERT_EQ(ackEvent.type(), bmqt::MessageEventType::e_ACK);
//       bmqa::MessageIterator mIter = ackEvent.messageIterator();
//       mIter.nextMessage();
//       ASSERT_EQ(mIter.message().ackStatus(), bmqt::AckResult::e_SUCCESS);
//
//       // This is a simple test.  After posting our message and receiving the
//       // ack, we are now shutting down our application.  Therefore we expect
//       // a 'stopAsync' call.
//       BMQA_EXPECT_CALL(mockSession, stopAsync());
//
//       // Now make a call to 'stopAsync' to stop our session.
//       mockSession.stopAsync();
//
//       // Here we are enqueuing an 'e_DISCONNECTED' event as you would
//       // receive from the broker on a successful shutdown.
//       mockSession.enqueueEvent(bmqa::MockSessionUtil::createSessionEvent(
//                                      bmqt::SessionEventType::e_DISCONNECTED,
//                                      0,   // statusCode
//                                      "",  // errorDescription
//                                      d_allocator_p));
//       ASSERT_EQ(mockSession.emitEvent(), true);
//
//       // Our event handler internally just stores the event emitted, so pop
//       // it out and examine.
//       bmqa::SessionEvent stopEvent(eventHandler.popSessionEvent());
//       ASSERT_EQ(stopEvent.type(), bmqt::SessionEventType::e_DISCONNECTED);
//       ASSERT_EQ(stopEvent.statusCode(), 0);
//
//      // The corresponding pendant operation of the 'initialize' which would
//      // need to be called only if 'initialize' was explicitly called.
//      // bmqa::MockSession::shutdown();
//   }
//..
//
///Example 2
///- - - - -
// The folowing example shows a consumer in synchronous mode, which will start
// the session, generate a push message (simulating the broker), confirm the
// message and then stop the session.  Additionally, this test case also sets
// all expectations up front before running the code, as this is the alternate
// way of writing your test driver.
//
// NOTE: Using 'enqueue' or 'emitEvent' on 'bmqa::MockSession' or 'emitting' on
//       the 'BMQA_EXPECT_CALL' macro in synchronous mode is meaningless.
//
//..
//  void unitTest()
//  {
//      // MockSession created without an eventHandler.
//      bmqa::MockSession mockSession(bmqt::SessionOptions(d_allocator_p),
//                                    d_allocator_p);
//
//      // The following static initializer method calls all the appropriate
//      // static initializers of the underlying components needed for the
//      // 'MockSession'.  The constructor of 'MockSession' will call it in
//      // any case but if events need to be built outside the scope of the
//      // creation of 'MockSession' you will need to explicitly invoke this
//      // static initializer method.
//      // bmqa::MockSession::initialize(s_allocator_p);
//
//      // Create simple queueIds and corrIds
//      bmqa::QueueId       queueId(1);
//      bmqt::CorrelationId corrId(1);
//
//      // Create the uri to your queue as you would in your application.
//      bmqt::Uri uri("bmq://my.domain/queue");
//
//      // Expecting that 'startAsync' will be called on the MockSession.
//      BMQA_EXPECT_CALL(mockSession, startAsync())
//          .returning(0);
//
//      // Simply creating a blob buffer factory on the stack to be used by
//      // 'createAckEvent'.  Typically you would have one for the component.
//      bdlbb::PooledBlobBufferFactory bufferFactory(4 * 1024, d_allocator_p);
//
//      // We then expect that 'nextEvent' will be called to return the
//      // 'e_CONNECTED' event from the broker
//      BMQA_EXPECT_CALL(mockSession, nextEvent(bsls::TimeInterval()))
//          .returning(bmqa::MockSessionUtil::createSessionEvent(
//                         bmqt::SessionEventType::e_CONNECTED,
//                         bmqt::CorrelationId::autoValue(),
//                         0,   // errorCode
//                         "",  // errorDescription
//                         d_allocator_p));
//          // Note that we use an 'autoValue' for correlationId because it's
//          // irrelevant for a 'CONNECTED' event.
//
//       // Initialize the queue flags for a consumer
//       bsls::Types::Uint64 flags = 0;
//       bmqt::QueueFlagsUtil::setReader(&flags);
//
//      // We use the macro to expect a call to 'openQueueSync', binding the
//      // 'uri' and 'queueId' objects as well as the flags that we created.
//      // Note that the 'queueId' object will be modified as 'openQueueSync'
//      // takes it as an output parameter.
//      bmqa::OpenQueueStatus expectedResult =
//          bmqa::MockSessionUtil::createOpenQueueStatus(
//                       queueId,
//                       bmqt::OpenQueueResult::e_SUCCESS,  // statusCode
//                       "",                                // errorDescription
//                       d_allocator_p);
//      BMQA_EXPECT_CALL(mockSession, openQueueSync(&queueId, uri, flags))
//          .returning(expectedResult);
//
//      // Build our incoming message event.
//      bsl::vector<bmqa::MockSessionUtil::PushMessageParams> pushMsgs(
//                                                              d_allocator_p);
//      bdlbb::Blob payload(&bufferFactory, d_allocator_p);
//      bdlbb::BlobUtil::append(&payload, "hello", 6);
//
//      const char        guidHex[] = "00000000000000000000000000000001";
//      bmqt::MessageGUID guid;
//      guid.fromHex(guidHex);
//
//      bmqa::MessageProperties properties;
//      mockSession.loadMessageProperties(&properties);
//
//      // For each message that we are supposed to receive from the broker,
//      // we need to specify the payload, the queueId, a guid (the hex is
//      // random but unique within your test driver) and properties which
//      // could be empty.
//      pushMsgs.emplace_back(payload, queueId, guid, properties);
//      bmqa::Event pushMsgEvent = bmqa::MockSessionUtil::createPushEvent(
//                                                              pushMsgs,
//                                                              &bufferFactory,
//                                                              d_allocator_p);
//      BMQA_EXPECT_CALL(mockSession, nextEvent(bsls::TimeInterval()))
//          .returning(pushMsgEvent);
//
//      // Next we expect a call to 'confirmMessages', to confirm the 1 message
//      // that we received from the broker.
//      bmqa::ConfirmEventBuilder confirmBuilder;
//      mockSession.loadConfirmEventBuilder(&confirmBuilder);
//      BMQA_EXPECT_CALL(mockSession, confirmMessages(&confirmBuilder))
//          .returning(0);
//
//      // Expectations have been set up.  Now we run the code.
//      // 'startAsync' is the first call.  We expect it to return 0 and we
//      // expect 'nextEvent' to return the 'e_CONNECTED' session event.
//      int rc = mockSession.startAsync();
//      ASSERT_EQ(rc, 0);
//      bmqa::SessionEvent startEvent = mockSession.nextEvent(
//                                                        bsls::TimeInterval())
//          .sessionEvent();
//      ASSERT_EQ(startEvent.type(), bmqt::SessionEventType::e_CONNECTED);
//      ASSERT_EQ(startEvent.statusCode(),       0);
//      ASSERT_EQ(startEvent.errorDescription(), "");
//
//      // Next we expect a call to 'openQueue' to open the queue.
//      bmqa::OpenQueueStatus result = mockSession.openQueueSync(&queueId,
//                                                               uri,
//                                                               10);
//      ASSERT_EQ(result, expectedResult);
//
//      // Now our call to 'nextEvent' will generate a push message from the
//      // broker, which we will then go on to confirm.
//      bmqa::MessageEvent pushMsgEvt(mockSession.nextEvent(
//                                                        bsls::TimeInterval())
//                                                            .messageEvent());
//      ASSERT_EQ(pushMsgEvt.type(), bmqt::MessageEventType::e_PUSH);
//
//      // Now that we have received a push message which has yet to be
//      // confirmed, we can confirm that 1 unconfirmed message exists.
//      ASSERT_EQ(mockSession.unconfirmedMessages(), 1U);
//
//      // Since there is only 1 message in our message event, we dont have to
//      // iterate over the event but in reality you will want to iterate over
//      // each message and add it to the confirm builder.
//      bmqa::MessageIterator mIter = pushMsgEvt.messageIterator();
//      mIter.nextMessage();
//      confirmBuilder.addMessageConfirmation(mIter.message());
//      ASSERT_EQ(confirmBuilder.messageCount(), 1);
//
//      // Confirm the messages using the builder that has been populated.
//      rc = mockSession.confirmMessages(&confirmBuilder);
//      ASSERT_EQ(rc, 0);
//
//      // Voila! We now have no unconfirmed messages.
//      ASSERT_EQ(mockSession.unconfirmedMessages(), 0u);
//      // 'stop' has been elided for brevity and is analogous to 'start'
//
//      // The corresponding pendant operation of the 'initialize' which would
//      // need to be called only if 'initialize' was explicitly called.
//      // bmqa::MockSession::shutdown();
//  }
//..
//
///Thread Safety
///-------------
// THREAD SAFE.

// BMQ
#include <bmqa_abstractsession.h>
#include <bmqa_closequeuestatus.h>
#include <bmqa_configurequeuestatus.h>
#include <bmqa_messageeventbuilder.h>
#include <bmqa_openqueuestatus.h>
#include <bmqa_queueid.h>
#include <bmqa_session.h>     // for 'bmqa::SessionEventHandler'
#include <bmqscm_version.h>
#include <bmqt_queueoptions.h>
#include <bmqt_sessionoptions.h>

// MWC
#include <mwcst_statcontext.h>

// BDE
#include <ball_log.h>
#include <bdlb_variant.h>
#include <bdlbb_blob.h>
#include <bdlbb_pooledblobbufferfactory.h>
#include <bsl_cstddef.h>
#include <bsl_deque.h>
#include <bsl_functional.h>
#include <bsl_memory.h>
#include <bsl_string.h>
#include <bsl_unordered_map.h>
#include <bsl_unordered_set.h>
#include <bslma_allocator.h>
#include <bslma_managedptr.h>
#include <bslma_usesbslmaallocator.h>
#include <bslmf_nestedtraitdeclaration.h>
#include <bslmt_mutex.h>
#include <bsls_alignedbuffer.h>
#include <bsls_assert.h>
#include <bsls_timeinterval.h>
#include <bsls_types.h>

namespace BloombergLP {

// FORWARD DECLARATION
namespace bmqimp { class Event; }
namespace bmqimp { class MessageCorrelationIdContainer; }
namespace bmqimp { struct Stat; }


namespace bmqa {

// FORWARD DECLARATION
class ConfirmEventBuilder;

// Record an expected method 'CALL' into the specified mock session 'OBJ'.
#define BMQA_EXPECT_CALL(OBJ, CALL)                                           \
    (((OBJ).expect_ ## CALL).fromLocation(__FILE__, __LINE__))


                            // ======================
                            // struct MockSessionUtil
                            // ======================

struct MockSessionUtil {
    // Utility methods to create 'bmqa' events

  private:
    // PRIVATE TYPES
    typedef bsl::shared_ptr<bmqimp::Event> EventImplSp;
                                        // Event impl shared pointer to access
                                        // the pimpl of 'bmqa::Event'.

    typedef bsl::shared_ptr<bmqimp::Queue> QueueImplSp;
                                        // Queue impl shared pointer to access
                                        // the pimpl of 'bmqa::QueueId'.

  public:
    // PUBLIC TYPES
    struct AckParams {
        // Struct representing parameters for an ack message.

        // PUBLIC DATA
        bmqt::AckResult::Enum d_status; // Status code

        bmqt::CorrelationId   d_correlationId;
                                        // Correlation id

        bmqt::MessageGUID     d_guid;   // Message GUID of confirmed message

        QueueId               d_queueId;
                                        // Queue id for message being referred
                                        // to

        // CREATORS
        AckParams(const bmqt::AckResult::Enum status,
                  const bmqt::CorrelationId&  correlationId,
                  const bmqt::MessageGUID&    guid,
                  const bmqa::QueueId&        queueId);
            // Create a new AckParams object with the specified 'status',
            // 'correlationId', 'guid' and 'queueId'.
    };

    // PUBLIC TYPES
    struct PushMessageParams {
        // Struct representing parameters for a push message.

        // PUBLIC DATA
        bdlbb::Blob       d_payload;   // Payload of message

        QueueId           d_queueId;    // Queue Id for this message

        bmqt::MessageGUID d_guid;       // GUID for message

        MessageProperties d_properties; // Optionally specified properties for
                                        // message

        // CREATORS
        PushMessageParams(const bdlbb::Blob&       payload,
                          const bmqa::QueueId&     queueId,
                          const bmqt::MessageGUID& guid,
                          const MessageProperties& properties);
            // Create a new PushMessageParams object with the specified
            // 'payload', 'queueId', 'guid' and 'properties'.
    };

    // CLASS METHODS
    static
    Event createAckEvent(const bsl::vector<AckParams>&  acks,
                         bdlbb::BlobBufferFactory      *bufferFactory,
                         bslma::Allocator              *allocator);
        // Create and return an 'Event' configured as a message event of type
        // 'e_ACK' with the specified 'acks' params using the specified
        // 'bufferFactory' and the specified 'allocator' to supply memory.

    static
    Event
    createPushEvent(const bsl::vector<PushMessageParams>&  pushEventParams,
                    bdlbb::BlobBufferFactory              *bufferFactory,
                    bslma::Allocator                      *allocator);
        // Create and return an 'Event' configured as a message event of type
        // 'e_PUSH' and with the specified 'pushEventParams', using the
        // specified 'bufferFactory' and the specified 'allocator' to supply
        // memory.

    static
    Event
    createQueueSessionEvent(bmqt::SessionEventType::Enum  sessionEventType,
                            QueueId                      *queueId,
                            const bmqt::CorrelationId&    correlationId,
                            int                           errorCode,
                            const bslstl::StringRef&      errorDescription,
                            bslma::Allocator             *allocator);
        // DEPRECATED: Use the 'createOpenQueueStatus(...)',
        //            'createConfigureQueueStatus(...)', or
        //            'createCloseQueueStatus(...)' methods instead.  This
        //            method will be marked as 'BSLS_ANNOTATION_DEPRECATED' in
        //            future release of libbmq.

    static
    Event createSessionEvent(bmqt::SessionEventType::Enum  sessionEventType,
                             const bmqt::CorrelationId&    correlationId,
                             const int                     errorCode,
                             const bslstl::StringRef&      errorDescription,
                             bslma::Allocator             *allocator);
        // Create and return an 'Event' configured as a session event with the
        // specified 'sessionEventType', 'errorCode' and 'errorDescription' and
        // using the specified 'allocator' to supply memory.  Note that this
        // method will not create queue related session events.

    static
    OpenQueueStatus
    createOpenQueueStatus(const QueueId&               queueId,
                          bmqt::OpenQueueResult::Enum  statusCode,
                          const bsl::string&           errorDescription,
                          bslma::Allocator            *allocator        = 0);
        // Create and return a 'bmqa::OpenQueueStatus' object with the
        // specified 'queueId', 'statusCode', and 'errorDescription', using the
        // optionally specified 'allocator' to supply memory in the result.

    static
    ConfigureQueueStatus
    createConfigureQueueStatus(
                       const QueueId&                    queueId,
                       bmqt::ConfigureQueueResult::Enum  statusCode,
                       const bsl::string&                errorDescription,
                       bslma::Allocator                 *allocator        = 0);
        // Create and return a 'bmqa::ConfigureQueueStatus' object with the
        // specified 'queueId', 'statusCode', and 'errorDescription', using the
        // optionally specified 'allocator' to supply memory in the result.

    static
    CloseQueueStatus
    createCloseQueueStatus(const QueueId&                queueId,
                           bmqt::CloseQueueResult::Enum  statusCode,
                           const bsl::string&            errorDescription,
                           bslma::Allocator             *allocator        = 0);
        // Create and return a 'bmqa::CloseQueueStatus' object with the
        // specified 'queueId', 'statusCode', and 'errorDescription', using the
        // optionally specified 'allocator' to supply memory in the result.
};


                             // =================
                             // class MockSession
                             // =================

class MockSession : public AbstractSession {
    // Mechanism to mock a 'bmqa::Session'

  public:
    // CLASS METHODS
    static void initialize(bslma::Allocator *allocator = 0);
        // Perform a one time initialization needed by components used in
        // 'MockSession' and 'MockSessionUtil'.  This method only needs to be
        // called once before any other method, but can be called multiple
        // times provided that for each call to 'initialize' there is a
        // corresponding call to 'shutdown'.  Use the optionally specified
        // 'allocator' for any memory allocation, or the 'global' allocator if
        // none is provided.  Note that specifying the allocator is provided
        // for test drivers only, and therefore users should let it default to
        // the global allocator.
        // NOTE: This method will need to be invoked only if the application
        //       needs to use 'MockSessionUtil' outside the scope of
        //       'MockSession'.

    static void shutdown();
        // Pendant operation of the 'initialize' one.  The number of calls to
        // 'shutdown' must equal the number of calls to 'initialize', without
        // corresponding 'shutdown' calls, to fully destroy the objects.  It is
        // safe to call 'initialize' after calling 'shutdown'.  The behaviour
        // is undefined if 'shutdown' is called without 'initialize' first
        // being called.

    // PUBLIC TYPES
    typedef bsl::function<void (const char *description,
                                const char *file,
                                int         line)> FailureCb;
        // Callback used to inform the test driver of an assertion failure.
        // The application test driver using the 'MockSession' may provide an
        // implementation that makes the test driver fail (for instance,
        // calling BDE's test driver ASSERT macro).

  private:
    // CLASS-SCOPE CATEGORY
    BALL_LOG_SET_CLASS_CATEGORY("BMQA.MOCKSESSION");

  private:
    // CONSTANTS
    static const int k_MAX_SIZEOF_MWCC_TWOKEYHASHMAP = 256;
        // Constant representing the maximum size of a 'mwcc::TwoKeyHashMap'
        // object, so that the below AlignedBuffer is big enough.  No 'mwc'
        // headers can be included in 'bmq' public headers, to prevent build
        // time dependency.

    // PRIVATE TYPES
    typedef bsls::AlignedBuffer<k_MAX_SIZEOF_MWCC_TWOKEYHASHMAP>
                                                           TwoKeyHashMapBuffer;
        // Aligned buffer holding the two key hash map

    typedef bsl::shared_ptr<bmqimp::Queue>                 QueueImplSp;
        // Cast for bmqimp::Queue to access internal data

    typedef bsl::shared_ptr<bmqimp::Event>                 EventImplSp;
        // Cast for bmqimp::Event to access internal data

    typedef bsl::shared_ptr<bmqimp::Stat>                  StatImplSp;
        // Convenience for bmqimp::Stat

    typedef bsl::shared_ptr<bmqimp::MessageCorrelationIdContainer>
                                                      CorrelationIdContainerSp;
        // Convenience for bmqimp::MessageCorrelationIdContainer

    typedef bsl::function<void()>                          CallbackFn;

    struct Job {
        // Struct holding attributes associated with an asynchronous queue
        // operation executed with a user-specified callback

        // PUBLIC DATA
        CallbackFn                   d_callback;
                                      // Signature of a 'void' callback method

        QueueImplSp                  d_queue;
                                      // Queue associated with this job

        bmqt::SessionEventType::Enum d_type;
                                      // Type of queue event associated with
                                      // this job (OPEN,CONFIGURE, or CLOSE)

        int                          d_status;
                                      // Status of the queue operation
    };

    typedef bdlb::Variant<Event, Job>                      EventOrJob;

    enum Method {
        // Enumeration for the methods in the 'MockSession' protocol.  Each
        // enum value corresponds to a method.
        e_START
      , e_START_ASYNC
      , e_STOP
      , e_STOP_ASYNC
      , e_FINALIZE_STOP
      , e_OPEN_QUEUE
      , e_OPEN_QUEUE_SYNC
      , e_OPEN_QUEUE_ASYNC
      , e_OPEN_QUEUE_ASYNC_CALLBACK
      , e_CLOSE_QUEUE
      , e_CLOSE_QUEUE_SYNC
      , e_CLOSE_QUEUE_ASYNC
      , e_CLOSE_QUEUE_ASYNC_CALLBACK
      , e_CONFIGURE_QUEUE
      , e_CONFIGURE_QUEUE_SYNC
      , e_CONFIGURE_QUEUE_ASYNC
      , e_CONFIGURE_QUEUE_ASYNC_CALLBACK
      , e_NEXT_EVENT
      , e_POST
      , e_CONFIRM_MESSAGE
      , e_CONFIRM_MESSAGES
    };

    struct Call {
        // PUBLIC TYPES
        typedef bsl::vector<EventOrJob>  EventsAndJobs; // Vector of events

        // PUBLIC DATA
        int                        d_rc;
                                        // Value to be returned on call

        Method                     d_method;
                                        // The type of method

        int                        d_line;
                                        // Line number

        bsl::string                d_file;
                                        // File

        bmqt::Uri                  d_uri;
                                        // Uri associated with this call

        bsls::Types::Uint64        d_flags;
                                        // Flags associated with this call

        bmqt::QueueOptions         d_queueOptions;
                                        // QueueOptions associated with this
                                        // call

        bsls::TimeInterval         d_timeout;
                                        // Timeout interval associated with
                                        // this call

        OpenQueueCallback          d_openQueueCallback;
                                        // Callback to be invoked upon emission
                                        // of an async openQueue (if callback
                                        // was provided)

        ConfigureQueueCallback     d_configureQueueCallback;
                                        // Callback to be invoked upon emission
                                        // of an async configureQueue (if
                                        // callback was provided)

        CloseQueueCallback         d_closeQueueCallback;
                                        // Callback to be invoked upon emission
                                        // of an async closeQueue (if callback
                                        // was provided)

        bmqa::OpenQueueStatus      d_openQueueResult;
                                        // The result of an open queue
                                        // operation

        bmqa::ConfigureQueueStatus d_configureQueueResult;
                                        // The result of a configure queue
                                        // operation

        bmqa::CloseQueueStatus     d_closeQueueResult;
                                        // The result of a close queue
                                        // operation

        EventsAndJobs              d_emittedEvents;
                                        // Events to be emitted on this call

        Event                      d_returnEvent;
                                        // Event to be returned on this call

        MessageEvent               d_messageEvent;
                                        // MessageEvent associated with this
                                        // call

        MessageConfirmationCookie  d_cookie;
                                        // MessageConfirmationCookie associated
                                        // with this call

        bslma::Allocator          *d_allocator_p;
                                        // Allocator

        // TRAITS
        BSLMF_NESTED_TRAIT_DECLARATION(Call, bslma::UsesBslmaAllocator)

        // CREATORS
        Call(Method method, bslma::Allocator *allocator);
            // Create a Call object having the specified 'method' and the
            // specified 'allocator' to supply memory.

        Call(const Call& other, bslma::Allocator *allocator);

        // MANIPULATORS
        Call& fromLocation(const char* file, int line);
            // Record the specified 'file' and 'line' that this call was
            // created from.
            //
            // NOTE: This function is invoked through the macro only.

        Call& returning(int rc);
            // Specify the return value for this function call to be the
            // specified 'rc'.

        Call& returning(const bmqa::OpenQueueStatus& result);
            // Specify the return value for this function call to be the
            // specified 'result'.  The behavior is undefined unless this call
            // corresponds to a synchronous open queue.

        Call& returning(const bmqa::ConfigureQueueStatus& result);
            // Specify the return value for this function call to be the
            // specified 'result'.  The behavior is undefined unless this call
            // corresponds to a synchronous configure queue.

        Call& returning(const bmqa::CloseQueueStatus& result);
            // Specify the return value for this function call to be the
            // specified 'result'.  The behavior is undefined unless this call
            // corresponds to a synchronous close queue.

        Call& returning(const Event& event);
            // Specify the return value for this function call to be the
            // specified 'event'.

        Call& emitting(const Event& event);
            // Specify the specified 'event' to be emitted on this function
            // call.

        Call& emitting(const OpenQueueStatus& openQueueResult);
            // Specify the specified 'openQueueResult' to be emitted on this
            // function call.  The behavior is undefined unless the call is an
            // 'openQueueAsync' provided a callback.

        Call& emitting(const ConfigureQueueStatus& configureQueueResutl);
            // Specify the specified 'configureQueueResult' to be emitted on
            // this function call.  The behavior is undefined unless the call
            // is a 'configureQueueResult' provided a callback.

        Call& emitting(const CloseQueueStatus& closeQueueResult);
            // Specify the specified 'closeQueueResult' to be emitted on this
            // function call.  The behavior is undefined unless the call is a
            // 'closeQueueResult' provided a callback.

        // ACCESSORS
        const char* methodName() const;
            // Get the method name of this call.
    };

    typedef bsl::deque<Call> CallQueue;
        // Queue of expected calls

    typedef bsl::deque<bmqa::MessageEvent> PostedEvents;
        // Queue of posted events

    // DATA
    bdlbb::PooledBlobBufferFactory          d_blobBufferFactory;
                                        // Buffer factory

    bslma::ManagedPtr<SessionEventHandler>  d_eventHandler_mp;
                                        // Event handler (set only in
                                        // asynchronous mode)

    mutable CallQueue                       d_calls;
                                        // sequence of method calls that are
                                        // expected

    mutable bsl::deque<EventOrJob>          d_eventsAndJobs;
                                        // events waiting to be emitted

    bsl::unordered_set<bmqt::MessageGUID>   d_unconfirmedGUIDs;
                                        // GUIDS of unconfirmed messages

    TwoKeyHashMapBuffer                     d_twoKeyHashMapBuffer;
                                        // Aligned buffer of two key hash map
                                        // uri, corrid to queues

    FailureCb                               d_failureCb;
                                        // Currently installed failure callback
                                        // that is invoked if methods are
                                        // called incorrectly or out of order.

    int                                     d_lastQueueId;
                                        // QueueId

    CorrelationIdContainerSp                d_corrIdContainer_sp;
                                        // Mock correlationId container

    PostedEvents                            d_postedEvents;
                                        // Queue of posted events

    mutable bslmt::Mutex                    d_mutex;
                                        // protects all public methods

    mwcst::StatContext                      d_rootStatContext;
                                        // Top level stat context for this
                                        // mocked Session.

    StatImplSp                              d_queuesStats_sp;
                                        // Stats for all queues

    bmqt::SessionOptions                    d_sessionOptions;
                                        // Session Options for current session

    bslma::Allocator                       *d_allocator_p;
                                        // Allocator

  private:
    // PRIVATE CLASS METHODS
    static const char *toAscii(const Method method);
        // Get the string representation of the specified 'method'.

    // PRIVATE MANIPULATORS
    void initializeStats();
        // Configure this component to keep track of statistics.

    void
    openQueueImp(QueueId                   *queueId,
                 const bmqt::QueueOptions&  options,
                 const bmqt::Uri&           uri,
                 bsls::Types::Uint64        flags,
                 bool                       async);
        // Open the specified 'queueId' with the specified 'uri' and 'flags'.
        // If the specified 'async' param is true, set the state of the
        // specified 'queueId' to 'e_OPENING', else set it to 'e_OPENED.  This
        // is to account for the fact that in 'openQueue' blocks until the
        // queue is opened whereas 'openQueueAsync' returns immediately and the
        // state changes to opened on emission of a successfull open queue
        // response.

    void processIfQueueEvent(Event *event);
        // Modify the queues specified by the 'event' if this event is a
        // session event of type 'e_QUEUE_OPEN_RESULT' or
        // 'e_QUEUE_CLOSE_RESULT'.

    void processIfQueueJob(Job *job);
        // Modify the queue(s) associated with the specified 'job' if this job
        // is associated with an operation of type 'e_QUEUE_OPEN_RESULT' or
        // 'e_QUEUE_CLOSE_RESULT'.

    void processIfPushEvent(const Event& event);
        // Record the messages in the specified 'event' as unconfirmed if it is
        // a push message event.

    // PRIVATE ACCESSORS
    void assertWrongCall(const Method method) const;
        // Invoke the failure callback because of a wrong call to the specified
        // 'method'.

    void assertWrongCall(const Method method, const Call&  expectedCall) const;
        // Invoke the failure callback because of a wrong call to the specified
        // 'method', while the specified 'expectedCall' was expected instead.

    template<class T, class U>
    void assertWrongArg(const T&      expected,
                        const U&      actual,
                        const Method  method,
                        const char   *arg,
                        const Call&   call) const;
        // Verify that the specified 'actual' value is equal to the specified
        // 'expected' value for argument 'argName' (a string) in the call to
        // 'method' (a Method enum) in 'call' (a Call).  Invoke the failure
        // callback if not.

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(MockSession, bslma::UsesBslmaAllocator)

    // CREATORS
    explicit
    MockSession(const bmqt::SessionOptions&  options = bmqt::SessionOptions(),
                bslma::Allocator            *allocator = 0);
        // Create a new 'MockSession' in *synchronous* mode using the
        // optionally specified 'options'.  In such mode, events have to be
        // fetched by the application using the 'nextEvent()' method.
        // Optionally specify an 'allocator' used to supply memory.  If
        // 'allocator' is 0, the currently installed default allocator is used.

    explicit
    MockSession(
      bslma::ManagedPtr<SessionEventHandler>  eventHandler,
      const bmqt::SessionOptions&             options = bmqt::SessionOptions(),
      bslma::Allocator                       *allocator = 0);
        // Create a 'MockSession' in *asynchronous* mode using the specified
        // 'eventHandler' as callback for event processing and the optionally
        // specified 'options'.  Optionally specify an 'allocator' used to
        // supply memory.  If the optionally specified 'allocator' is 0, the
        // currently installed default allocator is used.

    ~MockSession() BSLS_KEYWORD_OVERRIDE;
        // Stop the 'MockSession' and destroy this object.

    // MANIPULATORS
    //   (specific to 'bmqa::MockSession')
    void enqueueEvent(const bmqa::Event& event);
        // Enqueue the specified 'event' in the queue of events to be emitted.
        // NOTE: This method is unique to 'MockSession' and is valid only in
        //       unit tests.

    bool emitEvent(int numEvents = 1);
        // Emit the specified number of 'numEvents' from the queue of events to
        // be emitted.  Return true if at least one event was emitted, and
        // false otherwise.
        // NOTE: This method is unique to 'MockSession' and is valid only in
        //       unit tests.

    Call&
    expect_start(const bsls::TimeInterval& timeout = bsls::TimeInterval());
    Call&
    expect_startAsync(
                     const bsls::TimeInterval& timeout = bsls::TimeInterval());
    Call& expect_stop();
    Call& expect_stopAsync();
    Call& expect_finalizeStop();
    Call&
    expect_openQueue(
                    QueueId                   *queueId,
                    const bmqt::Uri&           uri,
                    bsls::Types::Uint64        flags,
                    const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval());
    Call&
    expect_openQueueSync(
                    QueueId                   *queueId,
                    const bmqt::Uri&           uri,
                    bsls::Types::Uint64        flags,
                    const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval());

    Call&
    expect_openQueueAsync(
                    QueueId                   *queueId,
                    const bmqt::Uri&           uri,
                    bsls::Types::Uint64        flags,
                    const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval());
    Call&
    expect_openQueueAsync(
                   QueueId                   *queueId,
                   const bmqt::Uri&           uri,
                   bsls::Types::Uint64        flags,
                   const OpenQueueCallback&   callback,
                   const bmqt::QueueOptions&  options  = bmqt::QueueOptions(),
                   const bsls::TimeInterval&  timeout  = bsls::TimeInterval());

    Call&
    expect_closeQueue(
                    QueueId                   *queueId,
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval());
    Call&
    expect_closeQueueSync(
                    QueueId                   *queueId,
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval());

    Call&
    expect_closeQueueAsync(
                    QueueId                   *queueId,
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval());
    Call&
    expect_closeQueueAsync(
                   QueueId                   *queueId,
                   const CloseQueueCallback&  callback,
                   const bsls::TimeInterval&  timeout  = bsls::TimeInterval());
    Call&
    expect_configureQueue(
                    QueueId                   *queueId,
                    const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval());
    Call&
    expect_configureQueueSync(
                    QueueId                   *queueId,
                    const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval());
    Call&
    expect_configureQueueAsync(
                    QueueId                   *queueId,
                    const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval());

    Call&
    expect_configureQueueAsync(
               QueueId                       *queueId,
               const bmqt::QueueOptions&      options,
               const ConfigureQueueCallback&  callback,
               const bsls::TimeInterval&      timeout  = bsls::TimeInterval());

    Call&
    expect_nextEvent(const bsls::TimeInterval& timeout = bsls::TimeInterval());
    Call& expect_post(const MessageEvent& messageEvent);
    Call& expect_confirmMessage(const Message& message);
    Call& expect_confirmMessage(const MessageConfirmationCookie& cookie);
    Call& expect_confirmMessages(ConfirmEventBuilder *builder);
        // Expect a call to the function and return a reference offering
        // modifiable access to the corresponding 'Call' object.
        //
        // NOTE: Do not use these method directly, use the macro
        //       'BMQA_EXPECT_CALL' instead.

    // MANIPULATORS
    //   (virtual bmqa::AbstractSession)
    int start(const bsls::TimeInterval& timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
    int startAsync(const bsls::TimeInterval& timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // Start the 'MockSession' with an optionally specified 'timeout'.  The
        // return values are elucidated in 'bmqt::GenericResult'.  In general a
        // call to 'start' or 'startAsync' emits a 'SessionEvent' of type
        // 'e_CONNECTED' or 'e_CONNECTION_TIMEOUT'.

    void stop() BSLS_KEYWORD_OVERRIDE;
    void stopAsync() BSLS_KEYWORD_OVERRIDE;
        // Stop the 'MockSession'.  In general a call to 'start' or
        // 'startAsync' emits a 'SessionEvent' of type 'e_DISCONNECTED' or
        // 'e_CONNECTION_TIMEOUT'.

    void finalizeStop() BSLS_KEYWORD_OVERRIDE;
        // This method is only to be used if the session is in synchronous mode
        // (i.e., not using the EventHandler): it must be called once all
        // threads getting events with 'nextEvent()' have been joined.

    void loadMessageEventBuilder(MessageEventBuilder *builder)
                                                         BSLS_KEYWORD_OVERRIDE;
        // Load the 'MessageEventBuilder' into the specified 'builder' output
        // parameter.

    void loadConfirmEventBuilder(ConfirmEventBuilder *builder)
                                                         BSLS_KEYWORD_OVERRIDE;
        // Load the 'ConfirmEventBuilder' into the specified 'builder' output
        // parameter.

    void loadMessageProperties(MessageProperties *buffer)
                                                         BSLS_KEYWORD_OVERRIDE;
        // Load the 'MessageProperties' into the specified 'buffer' output
        // parameter.

    ///Queue management
    ///----------------
    int getQueueId(QueueId *queueId, const bmqt::Uri& uri) const
                                                         BSLS_KEYWORD_OVERRIDE;
        // Load 'QueueId' object associated with the specified 'uri' into the
        // specified 'queueId' output parameter.

    int getQueueId(QueueId                    *queueId,
                   const bmqt::CorrelationId&  correlationId) const
                                                         BSLS_KEYWORD_OVERRIDE;
        // Load 'QueueId' object associated with the specified 'correlationId'
        // into the specified 'queueId' output parameter.

    int
    openQueue(QueueId                   *queueId,
              const bmqt::Uri&           uri,
              bsls::Types::Uint64        flags,
              const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
              const bsls::TimeInterval&  timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // DEPRECATED: Use the 'openQueueSync(QueueId *queueId...)' instead.
        //             This method will be marked as
        //             'BSLS_ANNOTATION_DEPRECATED' in future release of
        //             libbmq.

    OpenQueueStatus
    openQueueSync(QueueId                   *queueId,
                  const bmqt::Uri&           uri,
                  bsls::Types::Uint64        flags,
                  const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                  const bsls::TimeInterval&  timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // Open the queue having the specified 'uri' with the specified 'flags'
        // (a combination of the values defined in 'bmqt::QueueFlags::Enum'),
        // using the specified 'queueId' to correlate events related to that
        // queue.  The object 'queueId' referring to is modified, so the
        // 'queueId' represents the actual queue uri, flags and options.
        // Return a result providing the status and context of the operation.
        // Use the optionally specified 'options' to configure some advanced
        // settings.  In general, a call to 'openQueueSync' emits nothing.

    int
    openQueueAsync(QueueId                   *queueId,
                   const bmqt::Uri&           uri,
                   bsls::Types::Uint64        flags,
                   const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                   const bsls::TimeInterval&  timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // DEPRECATED: Use the 'openQueueAsync(...)' with callback flavor
        //             instead.  This method will be marked as
        //             'BSLS_ANNOTATION_DEPRECATED' in future release of
        //             libbmq.

    void
    openQueueAsync(QueueId                   *queueId,
                   const bmqt::Uri&           uri,
                   bsls::Types::Uint64        flags,
                   const OpenQueueCallback&   callback,
                   const bmqt::QueueOptions&  options  = bmqt::QueueOptions(),
                   const bsls::TimeInterval&  timeout  = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // Asynchronously open the queue having the specified 'uri' with the
        // specified 'flags' (a combination of the values defined in
        // 'bmqt::QueueFlags::Enum').  The object 'queueId' referring to is
        // modified, so the 'queueId' represents the actual queue uri, flags
        // and options.  The result of the operation is communicated to the
        // specified 'callback' via a 'bmqa::OpenQueueStatus', providing an
        // automatically generated correlation 'queueId' and the status and
        // context of the requested operation.  Use the optionally specified
        // 'options' to configure some advanced settings.  In general, a call
        // to 'openQueueAsync' does not emit a 'SessionEvent', but rather
        // invokes the 'callback' (if provided) instead when the corresponding
        // 'emitEvent' is called.
        //
        // NOTE: 'openQueueAsync' updates the queue state to 'e_OPENING' which
        //       is further updated upon invocation of the 'callback' (if
        //       provided) with a successful 'bmqa::OpenQueueStatus'.

    int
    configureQueue(QueueId                   *queueId,
                   const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                   const bsls::TimeInterval&  timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // DEPRECATED: Use the 'configureQueueSync(QueueId *queueId...)
        //             instead.  This method will be marked as
        //             'BSLS_ANNOTATION_DEPRECATED' in future release of
        //             libbmq.

    ConfigureQueueStatus
    configureQueueSync(const QueueId             *queueId,
                       const bmqt::QueueOptions&  options,
                       const bsls::TimeInterval&  timeout)
                                                         BSLS_KEYWORD_OVERRIDE;
        // Configure the queue identified by the specified 'queueId' using the
        // specified 'options' to configure some advanced settings and return a
        // result providing the status and context of the operation.  In
        // general, a call to 'configureQueueSync' emits nothing.

    int
    configureQueueAsync(
                    QueueId                   *queueId,
                    const bmqt::QueueOptions&  options = bmqt::QueueOptions(),
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // DEPRECATED: Use the 'configureQueueAsync(...)' with callback flavor
        //             instead.  This method will be marked as
        //             'BSLS_ANNOTATION_DEPRECATED' in future release of
        //             libbmq.

    void
    configureQueueAsync(
               const QueueId                 *queueId,
               const bmqt::QueueOptions&      options,
               const ConfigureQueueCallback&  callback,
               const bsls::TimeInterval&      timeout  = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // Asynchronously configure the queue identified by the specified
        // 'queueId' using the specified 'options' to configure some advanced
        // settings.  The result of the operation is communicated to the
        // specified 'callback' via a 'bmqa::ConfigureQueueStatus', providing
        // the status and context of the requested operation.  In general, a
        // call to 'configureQueueAsync' does not emit a 'SessionEvent', but
        // rather invokes the 'callback' (if provided) instead when the
        // corresponding 'emitEvent' is called.

    int
    closeQueue(QueueId                   *queueId,
               const bsls::TimeInterval&  timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // DEPRECATED: Use the 'closeQueueSync(QueueId *queueId...)' instead.
        //             This method will be marked as
        //             'BSLS_ANNOTATION_DEPRECATED' in future release of
        //             libbmq.

    CloseQueueStatus
    closeQueueSync(const QueueId             *queueId,
                   const bsls::TimeInterval&  timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // Close the queue identified by the specified 'queueId' using the
        // specified 'timeout'.  Populate the optionally specified 'result'
        // with the status and context of the operation and return a value
        // providing the status of the operation.  In general, a call to
        // 'closeQueueSync' emits nothing.

    int
    closeQueueAsync(QueueId                   *queueId,
                    const bsls::TimeInterval&  timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // DEPRECATED: Use the 'closeQueueAsync(...)' with callback flavor
        //             instead.  This method will be marked as
        //             'BSLS_ANNOTATION_DEPRECATED' in future release of
        //             libbmq.

    void
    closeQueueAsync(
                   const QueueId             *queueId,
                   const CloseQueueCallback&  callback,
                   const bsls::TimeInterval&  timeout  = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // Asynchronously close the queue identified by the specified 'queueId'
        // using the specified 'timeout'.  The result of the operation is
        // communicated to the specified 'callback' via a
        // 'bmqa::CloseQueueStatus', providing the status and context of the
        // requested operation.  In general, a call to 'closeQueueAsync' does
        // not emit a 'SessionEvent', but rather invokes the 'callback' (if
        // provided) instead when the corresponding 'emitEvent' is called.

    Event nextEvent(const bsls::TimeInterval& timeout = bsls::TimeInterval())
                                                         BSLS_KEYWORD_OVERRIDE;
        // Returns the nextEvent emitted.
        //
        // NOTE: This method should only be used when the 'MockSession' has
        //       been created in synchronous mode.  It is invalid if used in
        //       asynchronous mode and your test case is likely to be faulty
        //       if used with such a set up.

    int post(const MessageEvent& messageEvent) BSLS_KEYWORD_OVERRIDE;
        // Post the specified 'messageEvent'.  Return values are defined as per
        // 'bmqt::PostResult'.  In general a call to 'post' emits a
        // 'MessageEvent' of type 'e_ACK' or no response if acks are not
        // requested.

    int confirmMessage(const Message& message) BSLS_KEYWORD_OVERRIDE;
    int confirmMessage(const MessageConfirmationCookie& cookie)
                                                         BSLS_KEYWORD_OVERRIDE;
    int confirmMessages(ConfirmEventBuilder *builder) BSLS_KEYWORD_OVERRIDE;
        // Confirm messages specified by the 'message', 'cookie' or 'builder'.
        // Return values are defined as per 'bmqt::GenericResult'.  No event is
        // emitted for calls to 'confirmMessage'.

    int configureMessageDumping(const bslstl::StringRef& command)
                                                         BSLS_KEYWORD_OVERRIDE;
        // NOT IMPLEMENTED

    void setFailureCallback(const FailureCb& failureCb);
        // Set the failure callback of to be the specified 'failureCb'.  This
        // callback is invoked whenever an expectation set by the test driver
        // is not met.

    bool popPostedEvent(bmqa::MessageEvent *event);
        // Load into the specified 'event' the next posted event on this
        // session, if any, and return true; leave 'event' unchanged and return
        // false otherwise.

    // ACCESSORS
    size_t unconfirmedMessages() const;
        // Get the number of unconfirmed messages.  This corresponds to the
        // number of push messages enqueued to the session object but not yet
        // confirmed by the application.
};


// ============================================================================
//                             INLINE DEFINITIONS
// ============================================================================

                             // -----------------
                             // class MockSession
                             // -----------------

inline
void
MockSession::setFailureCallback(const FailureCb& failureCb)
{
    d_failureCb = failureCb;
}

inline
size_t
MockSession::unconfirmedMessages() const
{
    return d_unconfirmedGUIDs.size();
}

inline
bool
MockSession::popPostedEvent(bmqa::MessageEvent *event)
{
    // PRECONDITIONS
    BSLS_ASSERT(event);

    bslmt::LockGuard<bslmt::Mutex> guard(&d_mutex);                   // LOCKED

    if (d_postedEvents.empty()) {
        return false;                                                 // RETURN
    }

    *event = d_postedEvents.front();
    d_postedEvents.pop_front();

    return true;
}

}  // close package namespace
}  // close enterprise namespace

#endif