/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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 <folly/test/ObserverContainerTestUtil.h>

#include <folly/ObserverContainer.h>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>

using namespace folly;
using namespace ::testing;

/**
 *
 * Test components.
 *
 */

template <typename T>
std::vector<T*> uniquePtrVecToRawPtrVec(
    const std::vector<std::unique_ptr<T>>& input) {
  std::vector<T*> output;
  for (const auto& uptr : input) {
    output.push_back(uptr.get());
  }
  return output;
}

enum class TestObserverEvents : uint8_t {
  SpecialEvent = 1,
  SuperSpecialEvent = 2
};

using TestObserverContainerPolicy =
    ObserverContainerBasePolicyDefault<TestObserverEvents, 8>;

template <typename ObservedT>
class TestObserverInterface {
 public:
  virtual ~TestObserverInterface() = default;
  virtual void special(ObservedT* /* obj */) noexcept {}
  virtual void superSpecial(ObservedT* /* obj */) noexcept {}
  virtual void broadcast(ObservedT* /* obj */) noexcept {}
};

class TestSubject {
 public:
  TestSubject() : observerCtr(this) {}
  explicit TestSubject(TestSubject&& old)
      : observerCtr(this, std::move(old.observerCtr)) {}
  using ObserverContainer = ObserverContainer<
      TestObserverInterface<TestSubject>,
      TestSubject,
      TestObserverContainerPolicy>;

  void doSomethingSpecial() {
    observerCtr.invokeInterfaceMethod<TestObserverEvents::SpecialEvent>(
        [](auto observer, auto observed) { observer->special(observed); });
  }

  void doSomethingSuperSpecial() {
    observerCtr.invokeInterfaceMethod<TestObserverEvents::SuperSpecialEvent>(
        [](auto observer, auto observed) { observer->superSpecial(observed); });
  }

  void doBroadcast() { // no event enum associated, so "always on"
    observerCtr.invokeInterfaceMethodAllObservers(
        [](auto observer, auto observed) { observer->broadcast(observed); });
  }

  ObserverContainer observerCtr;
};

template <typename ObserverContainerT>
class MockTestSubjectObserver : public MockObserver<ObserverContainerT> {
 public:
  using TestSubjectT = typename ObserverContainerT::observed_type;

  // inherit constructor
  using MockObserver<ObserverContainerT>::MockObserver;

  MOCK_METHOD1(specialMock, void(TestSubjectT*));
  MOCK_METHOD1(superSpecialMock, void(TestSubjectT*));
  MOCK_METHOD1(broadcastMock, void(TestSubjectT*));

 private:
  void special(TestSubjectT* obj) noexcept override { specialMock(obj); }
  void superSpecial(TestSubjectT* obj) noexcept override {
    superSpecialMock(obj);
  }
  void broadcast(TestSubjectT* obj) noexcept override { broadcastMock(obj); }
};

template <typename ObserverContainerT>
class MockTestSubjectManagedObserver
    : public MockManagedObserver<ObserverContainerT> {
 public:
  using TestSubjectT = typename ObserverContainerT::observed_type;

  // inherit constructor
  using MockManagedObserver<ObserverContainerT>::MockManagedObserver;

  MOCK_METHOD1(specialMock, void(TestSubjectT*));
  MOCK_METHOD1(superSpecialMock, void(TestSubjectT*));
  MOCK_METHOD1(broadcastMock, void(TestSubjectT*));

 private:
  void special(TestSubjectT* obj) noexcept override { specialMock(obj); }
  void superSpecial(TestSubjectT* obj) noexcept override {
    superSpecialMock(obj);
  }
  void broadcast(TestSubjectT* obj) noexcept override { broadcastMock(obj); }
};

template <typename ObserverContainerT>
class MockTestSubjectManagedObserverSpecialized
    : public MockTestSubjectManagedObserver<ObserverContainerT> {
  using MockTestSubjectManagedObserver<
      ObserverContainerT>::MockTestSubjectManagedObserver;
};

/**
 *
 * Tests for ObserverContainer.
 *
 */

class ObserverContainerTest : public ::testing::Test {};

/**
 * Wrapping class for MockObserver managed with various smart pointers.
 *
 * Used to enable TYPED_TEST with different observer configurations.
 */
class WrappedMockObserver {
 public:
  enum class MockType { Strict };
  using EventSet = TestSubject::ObserverContainer::Observer::EventSet;
  using ObserverType = TestSubject::ObserverContainer::Observer;
  using MockObserverType =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;

  WrappedMockObserver() = default;
  virtual ~WrappedMockObserver() = default;

  /**
   * Release shared_ptr or unique_ptr for MockObserver object.
   *
   * If no other smart pointer is keeping the observer alive, the observer will
   * be destroyed.
   */
  virtual void release() = 0;

  /**
   * Get a reference to the mock object for use in EXPECT_CALL operations.
   */
  virtual MockObserverType& getMock() = 0;

  /**
   * Get a pointer to the observer object for use in lookup operations.
   *
   * Do not use this for addObserver or removeObserver. Instead, rely on the
   * implicit operator conversions.
   */
  virtual ObserverType* getPtr() = 0;
};

/*
 * Wrapping class for MockObserver managed with unique_ptr.
 */
class WrappedUniquePtrMockObserver : public WrappedMockObserver {
 public:
  WrappedUniquePtrMockObserver(const MockType mockType, const EventSet eventSet)
      : mockObserver_(makeMock(mockType, eventSet)) {}
  ~WrappedUniquePtrMockObserver() override = default;

  void release() override { mockObserver_.reset(); }

  MockObserverType& getMock() override { return *mockObserver_.get(); }

  MockObserverType* getPtr() override { return mockObserver_.get(); }

  /*
   * Conversion operator for use with addObserver() and removeObserver().
   */
  operator ObserverType*() { return mockObserver_.get(); }

 private:
  std::unique_ptr<MockObserverType> makeMock(
      MockType mockType, const EventSet eventSet) {
    switch (mockType) {
      case MockType::Strict:
        return std::make_unique<StrictMock<MockObserverType>>(eventSet);
    }
  }

  std::unique_ptr<MockObserverType> mockObserver_;
};

/*
 * Wrapping class for MockObserver managed with shared_ptr.
 */
class WrappedSharedPtrMockObserver : public WrappedMockObserver {
 public:
  WrappedSharedPtrMockObserver(const MockType mockType, const EventSet eventSet)
      : mockObserver_(makeMock(mockType, eventSet)) {}
  ~WrappedSharedPtrMockObserver() override = default;

  void release() override { mockObserver_.reset(); }

  MockObserverType& getMock() override { return *mockObserver_.get(); }

  MockObserverType* getPtr() override { return mockObserver_.get(); }

  /*
   * Conversion operator for use with addObserver() and removeObserver().
   */
  operator std::shared_ptr<ObserverType>() { return mockObserver_; }

 private:
  std::shared_ptr<MockObserverType> makeMock(
      MockType mockType, const EventSet eventSet) {
    switch (mockType) {
      case MockType::Strict:
        return std::make_shared<StrictMock<MockObserverType>>(eventSet);
    }
  }

  std::shared_ptr<MockObserverType> mockObserver_;
};

/*
 * Wrapping class for MockObserver managed with shared_ptr.
 *
 * The wrapper transfers ownership of the shared_ptr to the caller the first
 * time it is accessed, and relies on a weak_ptr to get a copy of the shared_ptr
 * from then onwards. This enables us to test scenarios where the only handle
 * keeping the observer alive is the one held by the container.
 */
class WrappedSharedPtrSingleHandleMockObserver : public WrappedMockObserver {
 public:
  WrappedSharedPtrSingleHandleMockObserver(
      const MockType mockType, const EventSet eventSet)
      : mockObserver_(makeMock(mockType, eventSet)),
        weakMockObserver_(mockObserver_) {}
  ~WrappedSharedPtrSingleHandleMockObserver() override = default;

  void release() override { mockObserver_.reset(); }

  MockObserverType& getMock() override {
    std::shared_ptr<MockObserverType> mockObserver(weakMockObserver_); // throws
    return *mockObserver.get();
  }

  MockObserverType* getPtr() override {
    std::shared_ptr<MockObserverType> mockObserver(weakMockObserver_); // throws
    return mockObserver.get();
  }

  /*
   * Conversion operator for use with addObserver() and removeObserver().
   *
   * The first call will transfer ownership of the shared_ptr to the caller.
   * Subsequent calls use the weak_ptr to get a shared_ptr.
   */
  operator std::shared_ptr<ObserverType>() {
    // first call, we transfer out the handle for the mock observer
    if (mockObserver_) {
      auto mockObserver = std::move(mockObserver_);
      mockObserver_.reset();
      return mockObserver;
    }

    // subsequent calls use the weak
    // this should work as long as the observer hasn't been destroyed
    std::shared_ptr<MockObserverType> mockObserver(weakMockObserver_); // throws
    return mockObserver;
  }

 private:
  std::shared_ptr<MockObserverType> makeMock(
      MockType mockType, const EventSet eventSet) {
    switch (mockType) {
      case MockType::Strict:
        return std::make_shared<StrictMock<MockObserverType>>(eventSet);
    }
  }

  std::shared_ptr<MockObserverType> mockObserver_;
  std::weak_ptr<MockObserverType> weakMockObserver_;
};

TEST(WrappedSharedPtrMockObserver, CheckBehavior) {
  using WrapperType = WrappedSharedPtrMockObserver;
  WrapperType wrappedObserver(
      WrapperType::MockType::Strict,
      WrapperType::ObserverType::EventSetBuilder().enableAllEvents().build());

  // verify that the handle is NOT moved to caller
  {
    std::shared_ptr<WrapperType::ObserverType> sptr = wrappedObserver;
    EXPECT_EQ(2, sptr.use_count()); // two handles
  }

  // test getMock
  {
    auto ptr = &wrappedObserver.getMock();
    std::shared_ptr<WrapperType::ObserverType> sptr = wrappedObserver;
    EXPECT_EQ(sptr.get(), ptr);
  }

  // test getPtr
  {
    auto ptr = wrappedObserver.getPtr();
    std::shared_ptr<WrapperType::ObserverType> sptr = wrappedObserver;
    EXPECT_EQ(sptr.get(), ptr);
  }

  // test release
  {
    std::shared_ptr<WrapperType::ObserverType> sptr = wrappedObserver;
    EXPECT_EQ(2, sptr.use_count()); // two handles
    wrappedObserver.release();
    EXPECT_EQ(1, sptr.use_count()); // one handle
  }
}

TEST(WrappedSharedPtrSingleHandleMockObserver, CheckBehavior) {
  using WrapperType = WrappedSharedPtrSingleHandleMockObserver;
  WrapperType wrappedObserver(
      WrapperType::MockType::Strict,
      WrapperType::ObserverType::EventSetBuilder().enableAllEvents().build());

  // verify that the handle is moved to caller
  std::shared_ptr<WrapperType::ObserverType> sptr = wrappedObserver;
  EXPECT_EQ(1, sptr.use_count()); // one handle

  // verify that wrapper returns shared_ptr via weak_ptr after first time
  {
    std::shared_ptr<WrapperType::ObserverType> sptr2 = wrappedObserver;
    EXPECT_EQ(sptr, sptr2);
    EXPECT_EQ(2, sptr.use_count()); // two handles
  }
  EXPECT_EQ(1, sptr.use_count()); // one handle

  // test getMock
  {
    auto ptr = &wrappedObserver.getMock();
    EXPECT_EQ(sptr.get(), ptr);
  }

  // test getPtr
  {
    auto ptr = wrappedObserver.getPtr();
    EXPECT_EQ(sptr.get(), ptr);
  }

  // test release
  {
    wrappedObserver.release();
    EXPECT_EQ(1, sptr.use_count()); // one handle

    // since we hold on to weak ptr, can still shared_ptr
    std::shared_ptr<WrapperType::ObserverType> sptr2 = wrappedObserver;
    EXPECT_EQ(sptr, sptr2);
    EXPECT_EQ(2, sptr.use_count()); // two handles
  }

  // destroy sptr, verify all functions fail
  sptr = nullptr;
  EXPECT_THROW(wrappedObserver.getMock(), std::bad_weak_ptr);
  EXPECT_THROW(wrappedObserver.getPtr(), std::bad_weak_ptr);
  EXPECT_THROW(
      std::shared_ptr<WrapperType::ObserverType> sptr2 = wrappedObserver,
      std::bad_weak_ptr);
}

template <class T>
class ObserverContainerTypedTest : public ObserverContainerTest {};
using ObserverContainerTypedTestTypes = testing::Types<
    WrappedUniquePtrMockObserver,
    WrappedSharedPtrMockObserver,
    WrappedSharedPtrSingleHandleMockObserver>;
TYPED_TEST_SUITE(ObserverContainerTypedTest, ObserverContainerTypedTestTypes);

/**
 * Ensure no issue if container is never used.
 */
TYPED_TEST(ObserverContainerTypedTest, CtrObserverNeverAttached) {
  auto obj1 = std::make_unique<TestSubject>();
}

/**
 * Ensure no issue if container is never used.
 */
TYPED_TEST(ObserverContainerTypedTest, CtrObserverNeverAttachedWithChecks) {
  auto obj1 = std::make_unique<TestSubject>();
  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());
}

/**
 * Test add / remove.
 */
TYPED_TEST(ObserverContainerTypedTest, CtrObserverAddRemove) {
  using WrappedMockObserverT = TypeParam;
  using EventSetBuilder =
      TestSubject::ObserverContainer::Observer::EventSetBuilder;
  using MockType = typename WrappedMockObserverT::MockType;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());

  WrappedMockObserverT observer1(
      MockType::Strict, EventSetBuilder().enableAllEvents().build());
  EXPECT_CALL(
      observer1.getMock(), addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(observer1.getMock(), attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1); // implicit cast per type

  EXPECT_EQ(1, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(observer1.getPtr()));

  EXPECT_CALL(observer1.getMock(), detachedMock(obj1.get()));
  EXPECT_CALL(
      observer1.getMock(),
      removedFromObserverContainerMock(&obj1->observerCtr));
  obj1->observerCtr.removeObserver(observer1); // implicit cast per type

  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());
}

/**
 * Test moving an ObserverContainer.
 *
 * Post move action: Observer removed and destroyed prior to subject destroy.
 */
TYPED_TEST(ObserverContainerTypedTest, CtrMoveThenRemoveAndDestroyObserver) {
  using WrappedMockObserverT = TypeParam;
  using EventSetBuilder =
      TestSubject::ObserverContainer::Observer::EventSetBuilder;
  using MockType = typename WrappedMockObserverT::MockType;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());

  WrappedMockObserverT observer1(
      MockType::Strict, EventSetBuilder().enableAllEvents().build());
  typename WrappedMockObserverT::ObserverType::Safety dc(observer1.getMock());
  EXPECT_CALL(
      observer1.getMock(), addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(observer1.getMock(), attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1); // implicit cast per type

  EXPECT_EQ(1, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(observer1.getPtr()));

  EXPECT_CALL(
      observer1.getMock(), movedToObserverContainerMock(&obj1->observerCtr, _));
  EXPECT_CALL(observer1.getMock(), movedMock(obj1.get(), _, _));

  auto obj2 = std::make_unique<TestSubject>(std::move(*obj1));

  EXPECT_EQ(obj2->observerCtr.numObservers(), 1);
  EXPECT_EQ(obj1->observerCtr.numObservers(), 0);

  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());

  EXPECT_THAT(
      obj2->observerCtr.getObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj2->observerCtr.findObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj2->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(observer1.getPtr()));

  EXPECT_CALL(observer1.getMock(), detachedMock(obj2.get()));
  EXPECT_CALL(
      observer1.getMock(),
      removedFromObserverContainerMock(&obj2->observerCtr));
  obj2->observerCtr.removeObserver(observer1); // implicit cast per type

  observer1.release(); // release the observer from wrapper; should destroy

  EXPECT_EQ(obj2->observerCtr.numObservers(), 0);
  EXPECT_EQ(obj1->observerCtr.numObservers(), 0);
}

/**
 * Test moving an ObserverContainer.
 *
 * Post move action: Observed object destroyed.
 */
TYPED_TEST(ObserverContainerTypedTest, CtrMoveThenDestroyObserved) {
  using WrappedMockObserverT = TypeParam;
  using EventSetBuilder =
      TestSubject::ObserverContainer::Observer::EventSetBuilder;
  using MockType = typename WrappedMockObserverT::MockType;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());

  WrappedMockObserverT observer1(
      MockType::Strict, EventSetBuilder().enableAllEvents().build());
  typename WrappedMockObserverT::ObserverType::Safety dc(observer1.getMock());
  EXPECT_CALL(
      observer1.getMock(), addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(observer1.getMock(), attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1); // implicit cast per type

  EXPECT_EQ(1, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(observer1.getPtr()));

  EXPECT_CALL(
      observer1.getMock(), movedToObserverContainerMock(&obj1->observerCtr, _));
  EXPECT_CALL(observer1.getMock(), movedMock(obj1.get(), _, _));

  auto obj2 = std::make_unique<TestSubject>(std::move(*obj1));

  EXPECT_EQ(obj2->observerCtr.numObservers(), 1);
  EXPECT_EQ(obj1->observerCtr.numObservers(), 0);

  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());

  EXPECT_THAT(
      obj2->observerCtr.getObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj2->observerCtr.findObservers(),
      UnorderedElementsAre(observer1.getPtr()));
  EXPECT_THAT(
      obj2->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(observer1.getPtr()));

  obj1 = nullptr; // nothing should occur

  EXPECT_CALL(observer1.getMock(), destroyedMock(obj2.get(), _));
  EXPECT_CALL(
      observer1.getMock(),
      removedFromObserverContainerMock(&obj2->observerCtr));
  obj2 = nullptr;
}

/**
 * Test moving a ObserverContainer with single handle shared_ptr observers.
 *
 * Verifies that moves work as expected when the shared_ptr within the container
 * is the only thing keeping the observer alive. `CtrMoveThenDestroyObserved`
 * typed test with type `WrappedSharedPtrSingleHandleMockObserver` also tests
 * this functionality; this is an additional explicit test.
 *
 * Post move action: Observed object destroyed.
 */
TEST_F(ObserverContainerTest, CtrWithSharedPtrMoveThenDestroyObserved) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());

  auto observer1 = std::make_shared<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  typename MockTestSubjectObserver::Safety dc(*observer1);
  EXPECT_CALL(*observer1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1);

  EXPECT_EQ(1, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(), UnorderedElementsAre(observer1.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers(), UnorderedElementsAre(observer1.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(observer1.get()));

  // now that observer1 is attached, we release shared_ptr but keep weak & raw
  // since the container holds shared_ptr too, observer should not be destroyed
  auto observer1Raw = observer1.get();
  std::weak_ptr<MockTestSubjectObserver> observer1Weak = observer1;
  observer1 = nullptr;
  ASSERT_FALSE(dc.destroyed()); // should still exist

  EXPECT_CALL(
      *observer1Raw, movedToObserverContainerMock(&obj1->observerCtr, _));
  EXPECT_CALL(*observer1Raw, movedMock(obj1.get(), _, _));

  auto obj2 = std::make_unique<TestSubject>(std::move(*obj1));

  EXPECT_EQ(obj2->observerCtr.numObservers(), 1);
  EXPECT_EQ(obj1->observerCtr.numObservers(), 0);

  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());

  EXPECT_THAT(
      obj2->observerCtr.getObservers(), UnorderedElementsAre(observer1Raw));
  EXPECT_THAT(
      obj2->observerCtr.findObservers(), UnorderedElementsAre(observer1Raw));
  EXPECT_THAT(
      obj2->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(observer1Raw));

  obj1 = nullptr; // nothing should occur

  EXPECT_CALL(*observer1Raw, destroyedMock(obj2.get(), _));
  EXPECT_CALL(
      *observer1Raw, removedFromObserverContainerMock(&obj2->observerCtr));
  obj2 = nullptr;
}

/**
 * Ensure correct behavior for invokeInterfaceMethod.
 */
TYPED_TEST(ObserverContainerTypedTest, CtrInvoke) {
  using WrappedMockObserverT = TypeParam;
  using EventSetBuilder =
      TestSubject::ObserverContainer::Observer::EventSetBuilder;
  using MockType = typename WrappedMockObserverT::MockType;
  InSequence s;

  // first invoke with no observers
  auto obj1 = std::make_unique<TestSubject>();
  obj1->doSomethingSpecial();
  obj1->doSomethingSuperSpecial();

  // now add an observer and hit the events again to ensure it works
  WrappedMockObserverT observer1(
      MockType::Strict, EventSetBuilder().enableAllEvents().build());
  observer1.getMock().useDefaultInvokeMockHandler();
  observer1.getMock().useDefaultPostInvokeMockHandler();
  EXPECT_CALL(
      observer1.getMock(), addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(observer1.getMock(), attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1); // implicit cast per type

  EXPECT_CALL(observer1.getMock(), specialMock(obj1.get()));
  EXPECT_CALL(observer1.getMock(), superSpecialMock(obj1.get()));
  obj1->doSomethingSpecial();
  obj1->doSomethingSuperSpecial();

  // remove and destroy the observer
  EXPECT_CALL(observer1.getMock(), detachedMock(obj1.get()));
  EXPECT_CALL(
      observer1.getMock(),
      removedFromObserverContainerMock(&obj1->observerCtr));
  obj1->observerCtr.removeObserver(observer1); // implicit cast per type
  observer1.release();

  // once more, with no observers
  obj1->doSomethingSpecial();
  obj1->doSomethingSuperSpecial();

  obj1 = nullptr;
}

/**
 * Ensure correct behavior for invokeInterfaceMethod after moving observers.
 */
TYPED_TEST(ObserverContainerTypedTest, CtrInvokeAfterMove) {
  using WrappedMockObserverT = TypeParam;
  using EventSetBuilder =
      TestSubject::ObserverContainer::Observer::EventSetBuilder;
  using MockType = typename WrappedMockObserverT::MockType;
  InSequence s;

  // first invoke with no observers
  auto obj1 = std::make_unique<TestSubject>();
  obj1->doSomethingSpecial();
  obj1->doSomethingSuperSpecial();

  // now add an observer and hit the events again to ensure it works
  WrappedMockObserverT observer1(
      MockType::Strict, EventSetBuilder().enableAllEvents().build());
  observer1.getMock().useDefaultInvokeMockHandler();
  observer1.getMock().useDefaultPostInvokeMockHandler();
  EXPECT_CALL(
      observer1.getMock(), addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(observer1.getMock(), attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1); // implicit cast per type

  EXPECT_CALL(observer1.getMock(), specialMock(obj1.get()));
  EXPECT_CALL(observer1.getMock(), superSpecialMock(obj1.get()));
  obj1->doSomethingSpecial();
  obj1->doSomethingSuperSpecial();

  // move the observer
  EXPECT_CALL(
      observer1.getMock(), movedToObserverContainerMock(&obj1->observerCtr, _));
  EXPECT_CALL(observer1.getMock(), movedMock(obj1.get(), _, _));

  auto obj2 = std::make_unique<TestSubject>(std::move(*obj1));

  EXPECT_EQ(obj2->observerCtr.numObservers(), 1);
  EXPECT_EQ(obj1->observerCtr.numObservers(), 0);

  // hit the events again after the move
  EXPECT_CALL(observer1.getMock(), specialMock(obj2.get()));
  EXPECT_CALL(observer1.getMock(), superSpecialMock(obj2.get()));
  obj2->doSomethingSpecial();
  obj2->doSomethingSuperSpecial();

  // hit the events again on obj1, nothing should happen
  EXPECT_CALL(observer1.getMock(), specialMock(obj1.get())).Times(0);
  EXPECT_CALL(observer1.getMock(), superSpecialMock(obj1.get())).Times(0);
  obj1->doSomethingSpecial();
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(observer1.getMock(), destroyedMock(obj2.get(), _));
  EXPECT_CALL(
      observer1.getMock(),
      removedFromObserverContainerMock(&obj2->observerCtr));
}

/**
 * Ensure that invokeInterfaceMethod handles EventSets properly.
 */
TEST_F(ObserverContainerTest, CtrInvokeMultipleObserversWithEventSets) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  // observer 1 is subscribed to nothing
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>());
  }

  // observer 2 is subscribed to SpecialEvent and SuperSpecialEvent explicitly
  // subscription is performed in two separate calls to enable()
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(TestObserverEvents::SpecialEvent);
    eventSet.enable(TestObserverEvents::SuperSpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 3 is subscribed to SpecialEvent and SuperSpecialEvent explicitly
  // subscription is performed in a single call to enable()
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(
        TestObserverEvents::SpecialEvent,
        TestObserverEvents::SuperSpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 4 is subscribed to all events via enableAllEvents()
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enableAllEvents();
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 5 is subscribed to just SpecialEvent
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(TestObserverEvents::SpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 6 is subscribed to just SuperSpecialEvent
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(TestObserverEvents::SuperSpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // add the observers
  for (const auto& observer : observers) {
    observer->useDefaultInvokeMockHandler();
    observer->useDefaultPostInvokeMockHandler();
    ;
    EXPECT_CALL(*observer, addedToObserverContainerMock(&obj1->observerCtr));
    EXPECT_CALL(*observer, attachedMock(obj1.get()));
    obj1->observerCtr.addObserver(observer.get());
  }
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAreArray(uniquePtrVecToRawPtrVec(observers)));

  // trigger multiple times
  for (auto i = 0; i < 3; i++) {
    // set up expectations, then trigger events
    //
    // everyone gets doBroadcast and...
    //// observer 1 should get no other events
    //// observer 2 -> 4 should get SpecialEvent + SuperSpecialEvent
    //// observer 5 should get SpecialEvent
    //// observer 6 should get SuperSpecialEvent
    EXPECT_CALL(*observers[0], broadcastMock(obj1.get()));
    EXPECT_CALL(*observers[1], broadcastMock(obj1.get()));
    EXPECT_CALL(*observers[2], broadcastMock(obj1.get()));
    EXPECT_CALL(*observers[3], broadcastMock(obj1.get()));
    EXPECT_CALL(*observers[4], broadcastMock(obj1.get()));
    EXPECT_CALL(*observers[5], broadcastMock(obj1.get()));
    obj1->doBroadcast();

    EXPECT_CALL(*observers[0], specialMock(obj1.get())).Times(0);
    EXPECT_CALL(*observers[1], specialMock(obj1.get()));
    EXPECT_CALL(*observers[2], specialMock(obj1.get()));
    EXPECT_CALL(*observers[3], specialMock(obj1.get()));
    EXPECT_CALL(*observers[4], specialMock(obj1.get()));
    EXPECT_CALL(*observers[5], specialMock(obj1.get())).Times(0);
    obj1->doSomethingSpecial();

    EXPECT_CALL(*observers[0], superSpecialMock(obj1.get())).Times(0);
    EXPECT_CALL(*observers[1], superSpecialMock(obj1.get()));
    EXPECT_CALL(*observers[2], superSpecialMock(obj1.get()));
    EXPECT_CALL(*observers[3], superSpecialMock(obj1.get()));
    EXPECT_CALL(*observers[4], superSpecialMock(obj1.get())).Times(0);
    EXPECT_CALL(*observers[5], superSpecialMock(obj1.get()));
    obj1->doSomethingSuperSpecial();
  }

  // destroy object
  for (const auto& observer : observers) {
    EXPECT_CALL(*observer, destroyedMock(obj1.get(), _));
    EXPECT_CALL(
        *observer, removedFromObserverContainerMock(&obj1->observerCtr));
  }
  obj1 = nullptr;
}

/**
 * Ensure that invokeInterfaceMethod and postInvokeInterfaceMethod are called.
 */
TEST_F(
    ObserverContainerTest,
    CtrInvokeMultipleObserversWithEventSetsOverrideInvoke) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  // observer 1 is subscribed to nothing
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>());
  }

  // observer 2 is subscribed to SpecialEvent and SuperSpecialEvent explicitly
  // subscription is performed in two separate calls to enable()
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(TestObserverEvents::SpecialEvent);
    eventSet.enable(TestObserverEvents::SuperSpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 3 is subscribed to SpecialEvent and SuperSpecialEvent explicitly
  // subscription is performed in a single call to enable()
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(
        TestObserverEvents::SpecialEvent,
        TestObserverEvents::SuperSpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 4 is subscribed to all events via enableAllEvents()
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enableAllEvents();
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 5 is subscribed to just SpecialEvent
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(TestObserverEvents::SpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 6 is subscribed to just SuperSpecialEvent
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(TestObserverEvents::SuperSpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // add the observers
  for (const auto& observer : observers) {
    EXPECT_CALL(*observer, addedToObserverContainerMock(&obj1->observerCtr));
    EXPECT_CALL(*observer, attachedMock(obj1.get()));
    obj1->observerCtr.addObserver(observer.get());
  }
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAreArray(uniquePtrVecToRawPtrVec(observers)));

  // trigger multiple times
  for (auto i = 0; i < 3; i++) {
    // set up expectations, then trigger events
    //
    // everyone gets doBroadcast and...
    //// observer 1 should get no other events
    //// observer 2 -> 4 should get SpecialEvent + SuperSpecialEvent
    //// observer 5 should get SpecialEvent
    //// observer 6 should get SuperSpecialEvent
    //
    // since we're not using the default handlers for invoke and postInvoke,
    // we should see the mocked invoke and postInvoke being called

    // broadcast
    EXPECT_CALL(
        *observers[0],
        invokeInterfaceMethodMock(
            obj1.get(), _, folly::Optional<TestObserverEvents>()));
    EXPECT_CALL(
        *observers[1],
        invokeInterfaceMethodMock(
            obj1.get(), _, folly::Optional<TestObserverEvents>()));
    EXPECT_CALL(
        *observers[2],
        invokeInterfaceMethodMock(
            obj1.get(), _, folly::Optional<TestObserverEvents>()));
    EXPECT_CALL(
        *observers[3],
        invokeInterfaceMethodMock(
            obj1.get(), _, folly::Optional<TestObserverEvents>()));
    EXPECT_CALL(
        *observers[4],
        invokeInterfaceMethodMock(
            obj1.get(), _, folly::Optional<TestObserverEvents>()));
    EXPECT_CALL(
        *observers[5],
        invokeInterfaceMethodMock(
            obj1.get(), _, folly::Optional<TestObserverEvents>()));
    EXPECT_CALL(*observers[0], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[1], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[2], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[3], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[4], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[5], postInvokeInterfaceMethodMock(obj1.get()));
    obj1->doBroadcast();

    // special event
    EXPECT_CALL(
        *observers[0],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SpecialEvent)))
        .Times(0);
    EXPECT_CALL(
        *observers[1],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SpecialEvent)));
    EXPECT_CALL(
        *observers[2],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SpecialEvent)));
    EXPECT_CALL(
        *observers[3],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SpecialEvent)));
    EXPECT_CALL(
        *observers[4],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SpecialEvent)));
    EXPECT_CALL(
        *observers[5],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SpecialEvent)))
        .Times(0);
    EXPECT_CALL(*observers[0], postInvokeInterfaceMethodMock(obj1.get()))
        .Times(0);
    EXPECT_CALL(*observers[1], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[2], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[3], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[4], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[5], postInvokeInterfaceMethodMock(obj1.get()))
        .Times(0);
    obj1->doSomethingSpecial();

    // super special event
    EXPECT_CALL(
        *observers[0],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SuperSpecialEvent)))
        .Times(0);
    EXPECT_CALL(
        *observers[1],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SuperSpecialEvent)));
    EXPECT_CALL(
        *observers[2],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SuperSpecialEvent)));
    EXPECT_CALL(
        *observers[3],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SuperSpecialEvent)));
    EXPECT_CALL(
        *observers[4],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SuperSpecialEvent)))
        .Times(0);
    EXPECT_CALL(
        *observers[5],
        invokeInterfaceMethodMock(
            obj1.get(),
            _,
            folly::Optional<TestObserverEvents>(
                TestObserverEvents::SuperSpecialEvent)));
    EXPECT_CALL(*observers[0], postInvokeInterfaceMethodMock(obj1.get()))
        .Times(0);
    EXPECT_CALL(*observers[1], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[2], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[3], postInvokeInterfaceMethodMock(obj1.get()));
    EXPECT_CALL(*observers[4], postInvokeInterfaceMethodMock(obj1.get()))
        .Times(0);
    EXPECT_CALL(*observers[5], postInvokeInterfaceMethodMock(obj1.get()));
    obj1->doSomethingSuperSpecial();
  }

  // destroy object
  for (const auto& observer : observers) {
    EXPECT_CALL(*observer, destroyedMock(obj1.get(), _));
    EXPECT_CALL(
        *observer, removedFromObserverContainerMock(&obj1->observerCtr));
  }
  obj1 = nullptr;
}

/**
 * Add observer during event processing.
 */
TEST_F(ObserverContainerTest, CtrInvokeAddObserverOnInvoke) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  obs1->useDefaultInvokeMockHandler();
  obs2->useDefaultInvokeMockHandler();
  obs3->useDefaultInvokeMockHandler();

  // add observers 1 and 2
  EXPECT_CALL(*obs1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs1.get());

  EXPECT_CALL(*obs2, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs2.get());

  EXPECT_CALL(*obs1, specialMock(obj1.get()))
      .Times(1)
      .WillOnce(InvokeWithoutArgs(
          [&obj1, &obs3]() { obj1->observerCtr.addObserver(obs3.get()); }));
  EXPECT_CALL(*obs3, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, attachedMock(obj1.get()));

  EXPECT_CALL(*obs2, specialMock(obj1.get()));
  EXPECT_CALL(*obs3, specialMock(obj1.get()));
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*obs1, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs2, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs3, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get()));
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(*obs1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs1, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs2, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs3, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
}

/**
 * Add two observers during event processing.
 */
TEST_F(ObserverContainerTest, CtrInvokeAddTwoObserversOnInvokeFirst) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs4 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  obs1->useDefaultInvokeMockHandler();
  obs2->useDefaultInvokeMockHandler();
  obs3->useDefaultInvokeMockHandler();
  obs4->useDefaultInvokeMockHandler();

  // add observers 1 and 2
  EXPECT_CALL(*obs1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs1.get());

  EXPECT_CALL(*obs2, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs2.get());

  // observer 1 will add observers 3 and 4
  EXPECT_CALL(*obs1, specialMock(obj1.get()))
      .Times(1)
      .WillOnce(InvokeWithoutArgs([&obj1, &obs3, &obs4]() {
        obj1->observerCtr.addObserver(obs3.get());
        obj1->observerCtr.addObserver(obs4.get());
      }));
  EXPECT_CALL(*obs3, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, attachedMock(obj1.get()));
  EXPECT_CALL(*obs4, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs4, attachedMock(obj1.get()));

  EXPECT_CALL(*obs2, specialMock(obj1.get()));
  EXPECT_CALL(*obs3, specialMock(obj1.get()));
  EXPECT_CALL(*obs4, specialMock(obj1.get()));
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs4, postInvokeInterfaceMethodMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*obs1, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs2, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs3, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs4, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs4, postInvokeInterfaceMethodMock(obj1.get()));
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(*obs1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs1, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs2, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs3, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs4, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs4, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
}

/**
 * Add two observers during event processing at two different points.
 */
TEST_F(ObserverContainerTest, CtrInvokeAddTwoObserversOnInvokeFirstSecond) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs4 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  obs1->useDefaultInvokeMockHandler();
  obs2->useDefaultInvokeMockHandler();
  obs3->useDefaultInvokeMockHandler();
  obs4->useDefaultInvokeMockHandler();

  // add observers 1 and 2
  EXPECT_CALL(*obs1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs1.get());

  EXPECT_CALL(*obs2, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs2.get());

  // observer 3 and 4 will be added by observers 1 and 3 respectively
  EXPECT_CALL(*obs1, specialMock(obj1.get()))
      .Times(1)
      .WillOnce(InvokeWithoutArgs(
          [&obj1, &obs3]() { obj1->observerCtr.addObserver(obs3.get()); }));
  EXPECT_CALL(*obs3, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, attachedMock(obj1.get()));

  EXPECT_CALL(*obs2, specialMock(obj1.get()));
  EXPECT_CALL(*obs3, specialMock(obj1.get()))
      .Times(1)
      .WillOnce(InvokeWithoutArgs(
          [&obj1, &obs4]() { obj1->observerCtr.addObserver(obs4.get()); }));
  EXPECT_CALL(*obs4, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs4, attachedMock(obj1.get()));
  EXPECT_CALL(*obs4, specialMock(obj1.get()));

  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs4, postInvokeInterfaceMethodMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*obs1, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs2, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs3, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs4, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs4, postInvokeInterfaceMethodMock(obj1.get()));
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(*obs1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs1, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs2, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs3, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs4, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs4, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
}

/**
 * Add observer during post event processing.
 */
TEST_F(ObserverContainerTest, CtrInvokeAddObserverOnPostInvoke) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  obs1->useDefaultInvokeMockHandler();
  obs2->useDefaultInvokeMockHandler();
  obs3->useDefaultInvokeMockHandler();

  // add observers 1 and 2
  EXPECT_CALL(*obs1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs1.get());

  EXPECT_CALL(*obs2, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs2.get());

  EXPECT_CALL(*obs1, specialMock(obj1.get()));
  EXPECT_CALL(*obs2, specialMock(obj1.get()));
  EXPECT_CALL(*obs3, specialMock(obj1.get())).Times(0); // not added yet

  // observer 3 will be added during post processing by observer 1
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()))
      .Times(1)
      .WillOnce(InvokeWithoutArgs(
          [&obj1, &obs3]() { obj1->observerCtr.addObserver(obs3.get()); }));
  EXPECT_CALL(*obs3, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, attachedMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  // post invoke won't be called on observer 3 since it was newly added
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get())).Times(0);
  obj1->doSomethingSpecial();

  EXPECT_CALL(*obs1, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs2, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs3, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get()));
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(*obs1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs1, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs2, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs3, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
}

/**
 * Remove observer during event processing.
 */
TEST_F(ObserverContainerTest, CtrInvokeRemoveObserverOnInvoke) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  obs1->useDefaultInvokeMockHandler();
  obs2->useDefaultInvokeMockHandler();
  obs3->useDefaultInvokeMockHandler();

  // add observers 1, 2, and 3
  EXPECT_CALL(*obs1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs1.get());

  EXPECT_CALL(*obs2, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs2.get());

  EXPECT_CALL(*obs3, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs3.get());

  // observer 3 will be removed during processing of the event by observer 1
  EXPECT_CALL(*obs1, specialMock(obj1.get()))
      .Times(1)
      .WillOnce(InvokeWithoutArgs(
          [&obj1, &obs3]() { obj1->observerCtr.removeObserver(obs3.get()); }));
  EXPECT_CALL(*obs3, detachedMock(obj1.get()));
  EXPECT_CALL(*obs3, removedFromObserverContainerMock(&obj1->observerCtr));

  EXPECT_CALL(*obs2, specialMock(obj1.get()));
  EXPECT_CALL(*obs3, specialMock(obj1.get())).Times(0);
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get())).Times(0);
  obj1->doSomethingSpecial();

  EXPECT_CALL(*obs1, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs2, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs3, superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get())).Times(0);
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(*obs1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs1, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs2, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
}

/**
 * Remove two observers during event processing.
 */
TEST_F(ObserverContainerTest, CtrInvokeRemoveTwoObserversOnInvoke) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs4 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  obs1->useDefaultInvokeMockHandler();
  obs2->useDefaultInvokeMockHandler();
  obs3->useDefaultInvokeMockHandler();
  obs4->useDefaultInvokeMockHandler();

  // add observers 1 - 4
  EXPECT_CALL(*obs1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs1.get());

  EXPECT_CALL(*obs2, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs2.get());

  EXPECT_CALL(*obs3, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs3.get());

  EXPECT_CALL(*obs4, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs4, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs4.get());

  // observers 3 and 4 removed during processing of the event by observer 1
  EXPECT_CALL(*obs1, specialMock(obj1.get()))
      .Times(1)
      .WillOnce(InvokeWithoutArgs([&obj1, &obs3, &obs4]() {
        obj1->observerCtr.removeObserver(obs3.get());
        obj1->observerCtr.removeObserver(obs4.get());
      }));
  EXPECT_CALL(*obs3, detachedMock(obj1.get()));
  EXPECT_CALL(*obs3, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs4, detachedMock(obj1.get()));
  EXPECT_CALL(*obs4, removedFromObserverContainerMock(&obj1->observerCtr));

  EXPECT_CALL(*obs2, specialMock(obj1.get()));
  EXPECT_CALL(*obs3, specialMock(obj1.get())).Times(0);
  EXPECT_CALL(*obs4, specialMock(obj1.get())).Times(0);
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get())).Times(0);
  EXPECT_CALL(*obs4, postInvokeInterfaceMethodMock(obj1.get())).Times(0);
  obj1->doSomethingSpecial();

  EXPECT_CALL(*obs1, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs2, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs3, superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*obs4, superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs4, postInvokeInterfaceMethodMock(obj1.get())).Times(0);
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(*obs1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs1, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs2, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
}

/**
 * Remove observer during post event processing.
 */
TEST_F(ObserverContainerTest, CtrInvokeRemoveObserverOnPostInvoke) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  obs1->useDefaultInvokeMockHandler();
  obs2->useDefaultInvokeMockHandler();
  obs3->useDefaultInvokeMockHandler();

  // add observers 1, 2, and 3
  EXPECT_CALL(*obs1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs1.get());

  EXPECT_CALL(*obs2, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs2.get());

  EXPECT_CALL(*obs3, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs3.get());

  EXPECT_CALL(*obs1, specialMock(obj1.get()));
  EXPECT_CALL(*obs2, specialMock(obj1.get()));
  EXPECT_CALL(*obs3, specialMock(obj1.get()));

  // observer 3 will be removed during post processing by observer 1
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()))
      .Times(1)
      .WillOnce(InvokeWithoutArgs(
          [&obj1, &obs3]() { obj1->observerCtr.removeObserver(obs3.get()); }));
  EXPECT_CALL(*obs3, detachedMock(obj1.get()));
  EXPECT_CALL(*obs3, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get())).Times(0);
  obj1->doSomethingSpecial();

  EXPECT_CALL(*obs1, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs2, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs3, superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get())).Times(0);
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(*obs1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs1, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs2, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
}

/**
 * Add and remove observer during event processing.
 */
TEST_F(ObserverContainerTest, CtrInvokeAddRemoveObserverOnInvoke) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  auto obs4 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  obs1->useDefaultInvokeMockHandler();
  obs2->useDefaultInvokeMockHandler();
  obs3->useDefaultInvokeMockHandler();
  obs4->useDefaultInvokeMockHandler();

  // add observers 1 - 3
  EXPECT_CALL(*obs1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs1.get());

  EXPECT_CALL(*obs2, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs2.get());

  EXPECT_CALL(*obs3, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs3, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(obs3.get());

  // remove observer 3, add observer 4 during event processing for observer 1
  EXPECT_CALL(*obs1, specialMock(obj1.get()))
      .Times(1)
      .WillOnce(InvokeWithoutArgs([&obj1, &obs3, &obs4]() {
        obj1->observerCtr.removeObserver(obs3.get());
        obj1->observerCtr.addObserver(obs4.get());
      }));
  EXPECT_CALL(*obs3, detachedMock(obj1.get()));
  EXPECT_CALL(*obs3, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs4, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs4, attachedMock(obj1.get()));

  EXPECT_CALL(*obs2, specialMock(obj1.get()));
  EXPECT_CALL(*obs3, specialMock(obj1.get())).Times(0); // removed
  EXPECT_CALL(*obs4, specialMock(obj1.get())); // added
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get()))
      .Times(0); // removed
  EXPECT_CALL(*obs4, postInvokeInterfaceMethodMock(obj1.get())); // added
  obj1->doSomethingSpecial();

  EXPECT_CALL(*obs1, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs2, superSpecialMock(obj1.get()));
  EXPECT_CALL(*obs3, superSpecialMock(obj1.get())).Times(0); // removed
  EXPECT_CALL(*obs4, superSpecialMock(obj1.get())); // added
  EXPECT_CALL(*obs1, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs2, postInvokeInterfaceMethodMock(obj1.get()));
  EXPECT_CALL(*obs3, postInvokeInterfaceMethodMock(obj1.get()))
      .Times(0); // removed
  EXPECT_CALL(*obs4, postInvokeInterfaceMethodMock(obj1.get())); // added
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(*obs1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs1, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs2, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs2, removedFromObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*obs4, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs4, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
}

TEST_F(ObserverContainerTest, CtrGetFindObserversWithDetach) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  using MockTestSubjectManagedObserverSpecialized =
      MockTestSubjectManagedObserverSpecialized<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();

  // should be no observers
  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers<>(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::ManagedObserver>(),
      IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<MockTestSubjectManagedObserverSpecialized>(),
      IsEmpty());

  // lambda for adding observer
  auto addObserver = [&obj1](auto& observer) {
    observer->useDefaultInvokeMockHandler();
    observer->useDefaultPostInvokeMockHandler();
    if constexpr (std::is_same_v<
                      decltype(*observer),
                      StrictMock<MockTestSubjectObserver>&>) {
      EXPECT_CALL(*observer, addedToObserverContainerMock(&obj1->observerCtr));
    }
    EXPECT_CALL(*observer, attachedMock(obj1.get()));
    obj1->observerCtr.addObserver(observer.get());
  };

  // lambda for removing observer
  auto removeObserver = [&obj1](auto& observer) {
    EXPECT_CALL(*observer, detachedMock(obj1.get()));
    if constexpr (std::is_same_v<
                      decltype(*observer),
                      StrictMock<MockTestSubjectObserver>&>) {
      EXPECT_CALL(
          *observer, removedFromObserverContainerMock(&obj1->observerCtr));
    }
    obj1->observerCtr.removeObserver(observer.get());
  };

  // observer 1 is a MockTestSubjectObserver
  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>();
  addObserver(obs1);
  EXPECT_EQ(1, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(), UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<>(), UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::ManagedObserver>(),
      IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<MockTestSubjectManagedObserverSpecialized>(),
      IsEmpty());

  // observer 2 is a MockTestSubjectManagedObserver
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>();
  addObserver(obs2);
  EXPECT_EQ(2, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(),
      UnorderedElementsAre(obs1.get(), obs2.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<>(),
      UnorderedElementsAre(obs1.get(), obs2.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(obs1.get(), obs2.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::ManagedObserver>(),
      UnorderedElementsAre(obs2.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      UnorderedElementsAre(obs2.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<MockTestSubjectManagedObserverSpecialized>(),
      IsEmpty());

  // observer 3 is another MockTestSubjectManagedObserver
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>();
  addObserver(obs3);
  EXPECT_EQ(3, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(),
      UnorderedElementsAre(obs1.get(), obs2.get(), obs3.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<>(),
      UnorderedElementsAre(obs1.get(), obs2.get(), obs3.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(obs1.get(), obs2.get(), obs3.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::ManagedObserver>(),
      UnorderedElementsAre(obs2.get(), obs3.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      UnorderedElementsAre(obs2.get(), obs3.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<MockTestSubjectManagedObserverSpecialized>(),
      IsEmpty());

  // observer 4 is a MockTestSubjectManagedObserverSpecialized
  auto obs4 =
      std::make_unique<StrictMock<MockTestSubjectManagedObserverSpecialized>>();
  addObserver(obs4);
  EXPECT_EQ(4, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(),
      UnorderedElementsAre(obs1.get(), obs2.get(), obs3.get(), obs4.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<>(),
      UnorderedElementsAre(obs1.get(), obs2.get(), obs3.get(), obs4.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(obs1.get(), obs2.get(), obs3.get(), obs4.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::ManagedObserver>(),
      UnorderedElementsAre(obs2.get(), obs3.get(), obs4.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      UnorderedElementsAre(obs2.get(), obs3.get(), obs4.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<MockTestSubjectManagedObserverSpecialized>(),
      UnorderedElementsAre(obs4.get()));

  // observer 5 is another MockTestSubjectManagedObserver
  auto obs5 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>();
  addObserver(obs5);
  EXPECT_EQ(5, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(),
      UnorderedElementsAre(
          obs1.get(), obs2.get(), obs3.get(), obs4.get(), obs5.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<>(),
      UnorderedElementsAre(
          obs1.get(), obs2.get(), obs3.get(), obs4.get(), obs5.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(
          obs1.get(), obs2.get(), obs3.get(), obs4.get(), obs5.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::ManagedObserver>(),
      UnorderedElementsAre(obs2.get(), obs3.get(), obs4.get(), obs5.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      UnorderedElementsAre(obs2.get(), obs3.get(), obs4.get(), obs5.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<MockTestSubjectManagedObserverSpecialized>(),
      UnorderedElementsAre(obs4.get()));

  // observer 6 is another MockTestSubjectManagedObserverSpecialized
  auto obs6 =
      std::make_unique<StrictMock<MockTestSubjectManagedObserverSpecialized>>();
  addObserver(obs6);
  EXPECT_EQ(6, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(),
      UnorderedElementsAre(
          obs1.get(),
          obs2.get(),
          obs3.get(),
          obs4.get(),
          obs5.get(),
          obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<>(),
      UnorderedElementsAre(
          obs1.get(),
          obs2.get(),
          obs3.get(),
          obs4.get(),
          obs5.get(),
          obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(
          obs1.get(),
          obs2.get(),
          obs3.get(),
          obs4.get(),
          obs5.get(),
          obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::ManagedObserver>(),
      UnorderedElementsAre(
          obs2.get(), obs3.get(), obs4.get(), obs5.get(), obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      UnorderedElementsAre(
          obs2.get(), obs3.get(), obs4.get(), obs5.get(), obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<MockTestSubjectManagedObserverSpecialized>(),
      UnorderedElementsAre(obs4.get(), obs6.get()));

  // remove observers 3 and 4
  removeObserver(obs3);
  removeObserver(obs4);
  EXPECT_EQ(4, obj1->observerCtr.numObservers());
  EXPECT_THAT(
      obj1->observerCtr.getObservers(),
      UnorderedElementsAre(obs1.get(), obs2.get(), obs5.get(), obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<>(),
      UnorderedElementsAre(obs1.get(), obs2.get(), obs5.get(), obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      UnorderedElementsAre(obs1.get(), obs2.get(), obs5.get(), obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::ManagedObserver>(),
      UnorderedElementsAre(obs2.get(), obs5.get(), obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAre(obs1.get()));
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      UnorderedElementsAre(obs2.get(), obs5.get(), obs6.get()));
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<MockTestSubjectManagedObserverSpecialized>(),
      UnorderedElementsAre(obs6.get()));

  // remove the rest of the observers
  removeObserver(obs1);
  removeObserver(obs2);
  removeObserver(obs5);
  removeObserver(obs6);

  // should be no observers
  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_THAT(obj1->observerCtr.getObservers(), IsEmpty());
  EXPECT_THAT(obj1->observerCtr.findObservers<>(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::Observer>(),
      IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<TestSubject::ObserverContainer::ManagedObserver>(),
      IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(), IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      IsEmpty());
  EXPECT_THAT(
      obj1->observerCtr
          .findObservers<MockTestSubjectManagedObserverSpecialized>(),
      IsEmpty());

  obj1 = nullptr;
}

TEST_F(ObserverContainerTest, CtrHasObserversForEvent) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();

  // should be no observers
  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_FALSE(obj1->observerCtr
                   .hasObserversForEvent<TestObserverEvents::SpecialEvent>());
  EXPECT_FALSE(
      obj1->observerCtr
          .hasObserversForEvent<TestObserverEvents::SuperSpecialEvent>());

  // lambda for adding observer
  auto addObserver = [&obj1](auto& observer) {
    observer->useDefaultInvokeMockHandler();
    observer->useDefaultPostInvokeMockHandler();
    if constexpr (std::is_same_v<
                      decltype(*observer),
                      StrictMock<MockTestSubjectObserver>&>) {
      EXPECT_CALL(*observer, addedToObserverContainerMock(&obj1->observerCtr));
    }
    EXPECT_CALL(*observer, attachedMock(obj1.get()));
    obj1->observerCtr.addObserver(observer.get());
  };

  // lambda for removing observer
  auto removeObserver = [&obj1](auto& observer) {
    EXPECT_CALL(*observer, detachedMock(obj1.get()));
    if constexpr (std::is_same_v<
                      decltype(*observer),
                      StrictMock<MockTestSubjectObserver>&>) {
      EXPECT_CALL(
          *observer, removedFromObserverContainerMock(&obj1->observerCtr));
    }
    obj1->observerCtr.removeObserver(observer.get());
  };

  // observer 1 has SpecialEvent
  auto obs1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder()
          .enable(TestObserverEvents::SpecialEvent)
          .build());
  addObserver(obs1);
  EXPECT_EQ(1, obj1->observerCtr.numObservers());
  EXPECT_TRUE(obj1->observerCtr
                  .hasObserversForEvent<TestObserverEvents::SpecialEvent>());
  EXPECT_FALSE(
      obj1->observerCtr
          .hasObserversForEvent<TestObserverEvents::SuperSpecialEvent>());

  // observer 2 has SuperSpecialEvent
  auto obs2 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder()
          .enable(TestObserverEvents::SuperSpecialEvent)
          .build());
  addObserver(obs2);
  EXPECT_EQ(2, obj1->observerCtr.numObservers());
  EXPECT_TRUE(obj1->observerCtr
                  .hasObserversForEvent<TestObserverEvents::SpecialEvent>());
  EXPECT_TRUE(
      obj1->observerCtr
          .hasObserversForEvent<TestObserverEvents::SuperSpecialEvent>());

  // observer 3 has SpecialEvent
  auto obs3 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder()
          .enable(TestObserverEvents::SpecialEvent)
          .build());
  addObserver(obs3);
  EXPECT_EQ(3, obj1->observerCtr.numObservers());
  EXPECT_TRUE(obj1->observerCtr
                  .hasObserversForEvent<TestObserverEvents::SpecialEvent>());
  EXPECT_TRUE(
      obj1->observerCtr
          .hasObserversForEvent<TestObserverEvents::SuperSpecialEvent>());

  // observer 4 has SuperSpecialEvent
  auto obs4 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder()
          .enable(TestObserverEvents::SuperSpecialEvent)
          .build());
  addObserver(obs4);
  EXPECT_EQ(4, obj1->observerCtr.numObservers());
  EXPECT_TRUE(obj1->observerCtr
                  .hasObserversForEvent<TestObserverEvents::SpecialEvent>());
  EXPECT_TRUE(
      obj1->observerCtr
          .hasObserversForEvent<TestObserverEvents::SuperSpecialEvent>());

  // remove observers 3 and 4
  removeObserver(obs3);
  removeObserver(obs4);
  EXPECT_EQ(2, obj1->observerCtr.numObservers());
  EXPECT_TRUE(obj1->observerCtr
                  .hasObserversForEvent<TestObserverEvents::SpecialEvent>());
  EXPECT_TRUE(
      obj1->observerCtr
          .hasObserversForEvent<TestObserverEvents::SuperSpecialEvent>());

  // remove the rest of the observers
  removeObserver(obs1);
  removeObserver(obs2);

  // should be no observers
  EXPECT_EQ(0, obj1->observerCtr.numObservers());
  EXPECT_FALSE(obj1->observerCtr
                   .hasObserversForEvent<TestObserverEvents::SpecialEvent>());
  EXPECT_FALSE(
      obj1->observerCtr
          .hasObserversForEvent<TestObserverEvents::SuperSpecialEvent>());

  obj1 = nullptr;
}

/**
 *
 * Tests for (raw) Observer interactions with ObserverContainer.
 *
 */

TEST_F(ObserverContainerTest, ObserverNeverAttachedToCtr) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
}

TEST_F(ObserverContainerTest, ObserverNeverAttachedToCtrNoEvents) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectObserver>>();
}

TEST_F(ObserverContainerTest, ObserverAttachedThenObjectDestroyed) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  MockTestSubjectObserver::Safety dc(*observer1.get());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_CALL(*observer1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());
  ASSERT_FALSE(dc.destroyed());

  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*observer1, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
  ASSERT_FALSE(dc.destroyed());
}

TEST_F(ObserverContainerTest, SharedPtrObserverAttachedThenObjectDestroyed) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_shared<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  MockTestSubjectObserver::Safety dc(*observer1.get());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_CALL(*observer1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1);
  EXPECT_FALSE(dc.destroyed());

  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*observer1, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
  EXPECT_FALSE(dc.destroyed());
}

TEST_F(
    ObserverContainerTest,
    SharedPtrObserverAttachedSingleHandleThenObjectDestroyed) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_shared<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  MockTestSubjectObserver::Safety dc(*observer1.get());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_CALL(*observer1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1);
  EXPECT_FALSE(dc.destroyed());

  // now that observer1 is attached, we release shared_ptr but keep raw ptr
  // since the container holds shared_ptr too, observer should not be destroyed
  auto observer1Raw = observer1.get();
  observer1 = nullptr;
  ASSERT_FALSE(dc.destroyed()); // should still exist

  EXPECT_CALL(*observer1Raw, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1Raw, destroyedMock(obj1.get(), _));
  EXPECT_CALL(
      *observer1Raw, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1 = nullptr;
  EXPECT_TRUE(dc.destroyed()); // destroyed when observer destroyed
}

TEST_F(ObserverContainerTest, ObserverAttachedThenObserverDetached) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_CALL(*observer1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1, detachedMock(obj1.get()));
  EXPECT_CALL(*observer1, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1->observerCtr.removeObserver(observer1.get());
}

TEST_F(ObserverContainerTest, SharedPtrObserverAttachedThenObserverDetached) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_shared<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_CALL(*observer1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1);

  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1, detachedMock(obj1.get()));
  EXPECT_CALL(*observer1, removedFromObserverContainerMock(&obj1->observerCtr));
  obj1->observerCtr.removeObserver(observer1);
}

TEST_F(
    ObserverContainerTest,
    SharedPtrObserverAttachedSingleHandleThenObserverDetached) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_shared<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  MockTestSubjectObserver::Safety dc(*observer1.get());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_CALL(*observer1, addedToObserverContainerMock(&obj1->observerCtr));
  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1);

  // now that observer1 is attached, we release shared_ptr but keep weak & raw
  // since the container holds shared_ptr too, observer should not be destroyed
  auto observer1Raw = observer1.get();
  std::weak_ptr<MockTestSubjectObserver> observer1Weak = observer1;
  observer1 = nullptr;
  ASSERT_FALSE(dc.destroyed()); // should still exist

  EXPECT_CALL(*observer1Raw, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1Raw, detachedMock(obj1.get()));
  EXPECT_CALL(
      *observer1Raw, removedFromObserverContainerMock(&obj1->observerCtr));
  auto observer1Locked = observer1Weak.lock(); // lock again for removal
  ASSERT_THAT(observer1Locked, Not(IsNull()));
  EXPECT_TRUE(obj1->observerCtr.removeObserver(observer1Locked));
}

TEST_F(ObserverContainerTest, ObserverAttachedEvents) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  // observer 1 is subscribed to nothing
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>());
  }

  // observer 2 is subscribed to both events explicitly
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(TestObserverEvents::SpecialEvent);
    eventSet.enable(TestObserverEvents::SuperSpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 3 is subscribed to both events explicitly at once
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(
        TestObserverEvents::SpecialEvent,
        TestObserverEvents::SuperSpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 4 is subscribed to all events via enableAllEvents()
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enableAllEvents();
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 5 is subscribed to just SpecialEvent
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(TestObserverEvents::SpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // observer 6 is subscribed to just SuperSpecialEvent
  {
    MockTestSubjectObserver::EventSet eventSet;
    eventSet.enable(TestObserverEvents::SuperSpecialEvent);
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(eventSet));
  }

  // add the observers
  for (const auto& observer : observers) {
    observer->useDefaultInvokeMockHandler();
    observer->useDefaultPostInvokeMockHandler();
    EXPECT_CALL(*observer, addedToObserverContainerMock(&obj1->observerCtr));
    EXPECT_CALL(*observer, attachedMock(obj1.get()));
    obj1->observerCtr.addObserver(observer.get());
  }
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAreArray(uniquePtrVecToRawPtrVec(observers)));

  // set up expectations, then trigger events
  //// observer 1 should get no events
  //// observer 2 -> 4 should get both events
  //// observer 5 should get SpecialEvent
  //// observer 6 should get SuperSpecialEvent
  EXPECT_CALL(*observers[0], specialMock(obj1.get())).Times(0);
  EXPECT_CALL(*observers[1], specialMock(obj1.get()));
  EXPECT_CALL(*observers[2], specialMock(obj1.get()));
  EXPECT_CALL(*observers[3], specialMock(obj1.get()));
  EXPECT_CALL(*observers[4], specialMock(obj1.get()));
  EXPECT_CALL(*observers[5], specialMock(obj1.get())).Times(0);
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observers[0], superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*observers[1], superSpecialMock(obj1.get()));
  EXPECT_CALL(*observers[2], superSpecialMock(obj1.get()));
  EXPECT_CALL(*observers[3], superSpecialMock(obj1.get()));
  EXPECT_CALL(*observers[4], superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*observers[5], superSpecialMock(obj1.get()));
  obj1->doSomethingSuperSpecial();

  // destroy object
  for (const auto& observer : observers) {
    EXPECT_CALL(*observer, destroyedMock(obj1.get(), _));
    EXPECT_CALL(
        *observer, removedFromObserverContainerMock(&obj1->observerCtr));
  }
  obj1 = nullptr;
}

TEST_F(ObserverContainerTest, ObserverAttachedEventsUseBuilder) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectObserver>> observers;

  // observer 1 is subscribed to nothing
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(
            MockTestSubjectObserver::EventSetBuilder().build()));
  }

  // observer 2 is subscribed to both events explicitly
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(
            MockTestSubjectObserver::EventSetBuilder()
                .enable(TestObserverEvents::SpecialEvent)
                .enable(TestObserverEvents::SuperSpecialEvent)
                .build()));
  }

  // observer 3 is subscribed to both events explicitly at once
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(
            MockTestSubjectObserver::EventSetBuilder()
                .enable(
                    TestObserverEvents::SpecialEvent,
                    TestObserverEvents::SuperSpecialEvent)
                .build()));
  }

  // observer 4 is subscribed to all events via enableAllEvents()
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(
            MockTestSubjectObserver::EventSetBuilder()
                .enableAllEvents()
                .build()));
  }

  // observer 5 is subscribed to just SpecialEvent
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(
            MockTestSubjectObserver::EventSetBuilder()
                .enable(TestObserverEvents::SpecialEvent)
                .build()));
  }

  // observer 6 is subscribed to just SuperSpecialEvent
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectObserver>>(
            MockTestSubjectObserver::EventSetBuilder()
                .enable(TestObserverEvents::SuperSpecialEvent)
                .build()));
  }

  // add the observers
  for (const auto& observer : observers) {
    observer->useDefaultInvokeMockHandler();
    observer->useDefaultPostInvokeMockHandler();
    EXPECT_CALL(*observer, addedToObserverContainerMock(&obj1->observerCtr));
    EXPECT_CALL(*observer, attachedMock(obj1.get()));
    obj1->observerCtr.addObserver(observer.get());
  }
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectObserver>(),
      UnorderedElementsAreArray(uniquePtrVecToRawPtrVec(observers)));

  // set up expectations, then trigger events
  //// observer 1 should get no events
  //// observer 2 -> 4 should get both events
  //// observer 5 should get SpecialEvent
  //// observer 6 should get SuperSpecialEvent
  EXPECT_CALL(*observers[0], specialMock(obj1.get())).Times(0);
  EXPECT_CALL(*observers[1], specialMock(obj1.get()));
  EXPECT_CALL(*observers[2], specialMock(obj1.get()));
  EXPECT_CALL(*observers[3], specialMock(obj1.get()));
  EXPECT_CALL(*observers[4], specialMock(obj1.get()));
  EXPECT_CALL(*observers[5], specialMock(obj1.get())).Times(0);
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observers[0], superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*observers[1], superSpecialMock(obj1.get()));
  EXPECT_CALL(*observers[2], superSpecialMock(obj1.get()));
  EXPECT_CALL(*observers[3], superSpecialMock(obj1.get()));
  EXPECT_CALL(*observers[4], superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*observers[5], superSpecialMock(obj1.get()));
  obj1->doSomethingSuperSpecial();

  // destroy object
  for (const auto& observer : observers) {
    EXPECT_CALL(*observer, destroyedMock(obj1.get(), _));
    EXPECT_CALL(
        *observer, removedFromObserverContainerMock(&obj1->observerCtr));
  }
  obj1 = nullptr;
}

/**
 * Tests for ManagedObserver interactions with ObserverContainer.
 */

TEST_F(ObserverContainerTest, ManagedObserverNeverAttached) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  auto observer1 =
      std::make_unique<StrictMock<MockTestSubjectManagedObserver>>();
}

TEST_F(ObserverContainerTest, ManagedObserverNeverAttachedWithChecks) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  auto observer1 =
      std::make_unique<StrictMock<MockTestSubjectManagedObserver>>();
  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());
}

TEST_F(ObserverContainerTest, ManagedObserverAttachedThenObserverDestroyed) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  observer1 = nullptr;
}

TEST_F(
    ObserverContainerTest, ManagedObserverAttachedThenObserverDetachedViaCtr) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1, detachedMock(obj1.get()));
  obj1->observerCtr.removeObserver(observer1.get());
  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());
}

TEST_F(ObserverContainerTest, ManagedObserverAttachViaListCalledTwice) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  // try calling a second time... nothing should happen
  obj1->observerCtr.addObserver(observer1.get());
}

TEST_F(
    ObserverContainerTest, ManagedObserverAttachCalledTwiceDifferentObjects) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  // call attach again, but different object, should die
  auto obj2 = std::make_unique<TestSubject>();
  EXPECT_DEATH(obj2->observerCtr.addObserver(observer1.get()), ".*");
}

TEST_F(ObserverContainerTest, ManagedObserverDetachCalledTwice) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_CALL(*observer1, detachedMock(obj1.get()));
  EXPECT_TRUE(observer1->detach());
  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  // try calling a second time... nothing should happen
  EXPECT_FALSE(observer1->detach());
}

TEST_F(ObserverContainerTest, ManagedObserverDetachCalledNeverAttached) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();
  EXPECT_FALSE(observer1->detach());
}

TEST_F(ObserverContainerTest, ManagedObserverMovesBetweenObjectsDetach) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1, detachedMock(obj1.get()));
  observer1->detach();
  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  // now bring up obj2

  auto obj2 = std::make_unique<TestSubject>();

  EXPECT_CALL(*observer1, attachedMock(obj2.get()));
  obj2->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj2.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj2.get()));
  obj2->doSomethingSpecial();

  EXPECT_CALL(*observer1, detachedMock(obj2.get()));
  observer1->detach();
  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());
}

TEST_F(ObserverContainerTest, ManagedObserverMovesBetweenObjectsDetachViaCtr) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1, detachedMock(obj1.get()));
  obj1->observerCtr.removeObserver(observer1.get());
  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  // now bring up obj2

  auto obj2 = std::make_unique<TestSubject>();

  EXPECT_CALL(*observer1, attachedMock(obj2.get()));
  obj2->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj2.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj2.get()));
  obj2->doSomethingSpecial();

  EXPECT_CALL(*observer1, detachedMock(obj2.get()));
  obj2->observerCtr.removeObserver(observer1.get());
  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());
}

TEST_F(ObserverContainerTest, ManagedObserverMovesBetweenObjectsDestroyObject) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1, destroyedMock(obj1.get(), _));
  obj1 = nullptr;

  // now bring up obj2

  auto obj2 = std::make_unique<TestSubject>();

  EXPECT_CALL(*observer1, attachedMock(obj2.get()));
  obj2->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj2.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj2.get()));
  obj2->doSomethingSpecial();

  EXPECT_CALL(*observer1, destroyedMock(obj2.get(), _));
  obj2 = nullptr;
}

TEST_F(
    ObserverContainerTest, ManagedObserverMovesBetweenObjectsDestroyObserver) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observer1, destroyedMock(obj1.get(), _));
  obj1 = nullptr;

  // now bring up obj2
  auto obj2 = std::make_unique<TestSubject>();

  EXPECT_CALL(*observer1, attachedMock(obj2.get()));
  obj2->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj2.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj2.get()));
  obj2->doSomethingSpecial();

  EXPECT_EQ(1, obj2->observerCtr.numObservers());
  observer1 = nullptr;
  EXPECT_EQ(0, obj2->observerCtr.numObservers());
}

TEST_F(
    ObserverContainerTest,
    ManagedObserverMovesBetweenObjectsViaConstructorThenDetach) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  // now bring up obj2
  EXPECT_CALL(*observer1, movedMock(obj1.get(), _, _));
  auto obj2 = std::make_unique<TestSubject>(std::move(*obj1));

  EXPECT_EQ(obj2.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj2.get()));
  obj2->doSomethingSpecial();

  EXPECT_CALL(*observer1, detachedMock(obj2.get()));
  observer1->detach();
  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());
}

TEST_F(
    ObserverContainerTest,
    ManagedObserverMovesBetweenObjectsViaConstructorThenDetachViaCtr) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  // now bring up obj2
  EXPECT_CALL(*observer1, movedMock(obj1.get(), _, _));
  auto obj2 = std::make_unique<TestSubject>(std::move(*obj1));

  EXPECT_EQ(obj2.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj2.get()));
  obj2->doSomethingSpecial();

  EXPECT_CALL(*observer1, detachedMock(obj2.get()));
  obj2->observerCtr.removeObserver(observer1.get());
  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());
}

TEST_F(
    ObserverContainerTest,
    ManagedObserverMovesBetweenObjectsViaConstructorThenDestroyObject) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  // now bring up obj2
  EXPECT_CALL(*observer1, movedMock(obj1.get(), _, _));
  auto obj2 = std::make_unique<TestSubject>(std::move(*obj1));

  EXPECT_EQ(obj2.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj2.get()));
  obj2->doSomethingSpecial();

  EXPECT_CALL(*observer1, destroyedMock(obj2.get(), _));
  obj2 = nullptr;
}

TEST_F(
    ObserverContainerTest,
    ManagedObserverMovesBetweenObjectsViaConstructorThenDestroyObserver) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  auto observer1 = std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
      MockTestSubjectManagedObserver::EventSetBuilder()
          .enableAllEvents()
          .build());
  observer1->useDefaultInvokeMockHandler();
  observer1->useDefaultPostInvokeMockHandler();

  EXPECT_EQ(nullptr, observer1->getObservedObject());
  EXPECT_FALSE(observer1->isObserving());

  EXPECT_CALL(*observer1, attachedMock(obj1.get()));
  obj1->observerCtr.addObserver(observer1.get());

  EXPECT_EQ(obj1.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj1.get()));
  obj1->doSomethingSpecial();

  // now bring up obj2
  EXPECT_CALL(*observer1, movedMock(obj1.get(), _, _));
  auto obj2 = std::make_unique<TestSubject>(std::move(*obj1));

  EXPECT_EQ(obj2.get(), observer1->getObservedObject());
  EXPECT_TRUE(observer1->isObserving());
  EXPECT_CALL(*observer1, specialMock(obj2.get()));
  obj2->doSomethingSpecial();

  EXPECT_EQ(1, obj2->observerCtr.numObservers());
  observer1 = nullptr;
  EXPECT_EQ(0, obj2->observerCtr.numObservers());
}

TEST_F(ObserverContainerTest, ManagedObserverAttachedEventsUseBuilder) {
  using MockTestSubjectManagedObserver =
      MockTestSubjectManagedObserver<TestSubject::ObserverContainer>;
  InSequence s;

  auto obj1 = std::make_unique<TestSubject>();
  std::vector<std::unique_ptr<MockTestSubjectManagedObserver>> observers;

  // observer 1 is subscribed to nothing
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
            MockTestSubjectManagedObserver::EventSetBuilder().build()));
  }

  // observer 2 is subscribed to both events explicitly
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
            MockTestSubjectManagedObserver::EventSetBuilder()
                .enable(TestObserverEvents::SpecialEvent)
                .enable(TestObserverEvents::SuperSpecialEvent)
                .build()));
  }

  // observer 3 is subscribed to just SpecialEvent
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
            MockTestSubjectManagedObserver::EventSetBuilder()
                .enable(TestObserverEvents::SpecialEvent)
                .build()));
  }

  // observer 4 is subscribed to just SuperSpecialEvent
  {
    observers.emplace_back(
        std::make_unique<StrictMock<MockTestSubjectManagedObserver>>(
            MockTestSubjectManagedObserver::EventSetBuilder()
                .enable(TestObserverEvents::SuperSpecialEvent)
                .build()));
  }

  // add the observers
  for (const auto& observer : observers) {
    observer->useDefaultInvokeMockHandler();
    observer->useDefaultPostInvokeMockHandler();
    EXPECT_CALL(*observer, attachedMock(obj1.get()));
    obj1->observerCtr.addObserver(observer.get());
  }
  EXPECT_THAT(
      obj1->observerCtr.findObservers<MockTestSubjectManagedObserver>(),
      UnorderedElementsAreArray(uniquePtrVecToRawPtrVec(observers)));

  // set up expectations, then trigger events
  //// observer 1 should get no events
  //// observer 2 should get both events
  //// observer 3 should get SpecialEvent
  //// observer 4 should get SuperSpecialEvent
  EXPECT_CALL(*observers[0], specialMock(obj1.get())).Times(0);
  EXPECT_CALL(*observers[1], specialMock(obj1.get()));
  EXPECT_CALL(*observers[2], specialMock(obj1.get()));
  EXPECT_CALL(*observers[3], specialMock(obj1.get())).Times(0);
  obj1->doSomethingSpecial();

  EXPECT_CALL(*observers[0], superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*observers[1], superSpecialMock(obj1.get()));
  EXPECT_CALL(*observers[2], superSpecialMock(obj1.get())).Times(0);
  EXPECT_CALL(*observers[3], superSpecialMock(obj1.get()));
  obj1->doSomethingSuperSpecial();

  // destroy object
  for (const auto& observer : observers) {
    EXPECT_CALL(*observer, destroyedMock(obj1.get(), _));
  }
  obj1 = nullptr;
}

TEST_F(ObserverContainerTest, AddConstructorCallback) {
  uint64_t numSubjectsCreated = 0;
  std::vector<TestSubject*> subjectsCreated;
  auto callbackF = [&](TestSubject::ObserverContainer* ctr) {
    subjectsCreated.push_back(ctr->getObject());
    numSubjectsCreated++;
  };

  TestSubject::ObserverContainer::addConstructorCallback(callbackF);

  EXPECT_EQ(0, numSubjectsCreated);
  auto obj1 = std::make_unique<TestSubject>();
  EXPECT_EQ(1, numSubjectsCreated);
  auto obj2 = std::make_unique<TestSubject>();
  EXPECT_EQ(2, numSubjectsCreated);
  EXPECT_THAT(subjectsCreated, ElementsAre(obj1.get(), obj2.get()));

  obj1 = nullptr;
  obj2 = nullptr;
  EXPECT_EQ(2, numSubjectsCreated);
}

TEST_F(ObserverContainerTest, AddConstructorCallbackMulti) {
  uint64_t numSubjectsCreated1 = 0;
  std::vector<TestSubject*> subjectsCreated1;
  auto callbackF1 = [&](TestSubject::ObserverContainer* ctr) {
    subjectsCreated1.push_back(ctr->getObject());
    numSubjectsCreated1++;
  };

  uint64_t numSubjectsCreated2 = 0;
  std::vector<TestSubject*> subjectsCreated2;
  auto callbackF2 = [&](TestSubject::ObserverContainer* ctr) {
    subjectsCreated2.push_back(ctr->getObject());
    numSubjectsCreated2++;
  };

  TestSubject::ObserverContainer::addConstructorCallback(callbackF1);
  TestSubject::ObserverContainer::addConstructorCallback(callbackF2);

  EXPECT_EQ(0, numSubjectsCreated1);
  EXPECT_EQ(0, numSubjectsCreated2);
  EXPECT_THAT(subjectsCreated1, IsEmpty());
  EXPECT_THAT(subjectsCreated2, IsEmpty());
  auto obj1 = std::make_unique<TestSubject>();
  EXPECT_EQ(1, numSubjectsCreated1);
  EXPECT_EQ(1, numSubjectsCreated2);
  EXPECT_THAT(subjectsCreated1, ElementsAre(obj1.get()));
  EXPECT_THAT(subjectsCreated2, ElementsAre(obj1.get()));
  auto obj2 = std::make_unique<TestSubject>();
  EXPECT_EQ(2, numSubjectsCreated1);
  EXPECT_EQ(2, numSubjectsCreated2);
  EXPECT_THAT(subjectsCreated1, ElementsAre(obj1.get(), obj2.get()));
  EXPECT_THAT(subjectsCreated2, ElementsAre(obj1.get(), obj2.get()));

  obj1 = nullptr;
  obj2 = nullptr;
  EXPECT_EQ(2, numSubjectsCreated1);
  EXPECT_EQ(2, numSubjectsCreated2);
}

TEST_F(ObserverContainerTest, AddConstructorCallbackAttachObserver) {
  using MockTestSubjectObserver =
      MockTestSubjectObserver<TestSubject::ObserverContainer>;

  auto obs = std::make_unique<StrictMock<MockTestSubjectObserver>>(
      MockTestSubjectObserver::EventSetBuilder().enableAllEvents().build());
  obs->useDefaultInvokeMockHandler();
  obs->useDefaultPostInvokeMockHandler();

  uint64_t numSubjectsCreated = 0;
  auto callbackF = [&](TestSubject::ObserverContainer* ctr) {
    numSubjectsCreated++;
    ctr->addObserver(obs.get());
  };

  TestSubject::ObserverContainer::addConstructorCallback(callbackF);

  TestSubject::ObserverContainer::ContainerBase* ctrPtr;
  TestSubject* objPtr;
  EXPECT_CALL(*obs, addedToObserverContainerMock(_))
      .WillOnce(testing::SaveArg<0>(&ctrPtr));
  EXPECT_CALL(*obs, attachedMock(_)).WillOnce(testing::SaveArg<0>(&objPtr));

  auto obj1 = std::make_unique<TestSubject>();
  EXPECT_EQ(&obj1->observerCtr, ctrPtr);
  EXPECT_EQ(obj1.get(), objPtr);
  EXPECT_EQ(1, numSubjectsCreated);

  EXPECT_CALL(*obs, specialMock(obj1.get()));
  EXPECT_CALL(*obs, superSpecialMock(obj1.get()));
  obj1->doSomethingSpecial();
  obj1->doSomethingSuperSpecial();

  EXPECT_CALL(*obs, destroyedMock(obj1.get(), _));
  EXPECT_CALL(*obs, removedFromObserverContainerMock(ctrPtr));
  obj1 = nullptr;
}

/**
 *
 * Tests for ObserverContainerStore.
 *
 */

class ObserverContainerStoreTest : public ::testing::Test {
 protected:
  class TestStoreObserver {
   public:
    void incrementCount() { count_++; }

    uint64_t getCount() const { return count_; }

   private:
    uint64_t count_{0};
  };

  using Store = ObserverContainerStore<TestStoreObserver>;
};

TEST_F(ObserverContainerStoreTest, SizeInvokeEmpty) {
  Store store;
  EXPECT_EQ(0, store.size());
  store.invokeForEachObserver(
      [](auto* observer) { observer->incrementCount(); },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
}

TEST_F(ObserverContainerStoreTest, AddRemoveSize) {
  Store store;

  // add observer 1, then remove
  auto obs1 = std::make_shared<TestStoreObserver>();
  EXPECT_EQ(0, store.size());
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(0, store.size());

  // add observer 2, then remove
  auto obs2 = std::make_shared<TestStoreObserver>();
  EXPECT_EQ(0, store.size());
  EXPECT_TRUE(store.add(obs2));
  EXPECT_EQ(1, store.size());
  EXPECT_TRUE(store.remove(obs2));
  EXPECT_EQ(0, store.size());

  // add observer 1 and 2, then remove
  EXPECT_EQ(0, store.size());
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  EXPECT_TRUE(store.add(obs2));
  EXPECT_EQ(2, store.size());
  EXPECT_TRUE(store.remove(obs2));
  EXPECT_EQ(1, store.size());
  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(0, store.size());
}

TEST_F(ObserverContainerStoreTest, AddRemoveInvokeSize) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();
  auto obs2 = std::make_shared<TestStoreObserver>();
  EXPECT_EQ(0, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());
  EXPECT_EQ(0, obs1->getCount()); // sanity check no side effects on getCount()
  EXPECT_EQ(0, obs2->getCount()); // sanity check no side effects on getCount()

  // add observer 1, invoke, then remove
  EXPECT_EQ(0, store.size());
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  store.invokeForEachObserver(
      [](auto* observer) { observer->incrementCount(); },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
  EXPECT_EQ(1, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());

  EXPECT_TRUE(store.remove(obs1));
  store.invokeForEachObserver(
      [](auto* observer) { observer->incrementCount(); },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
  EXPECT_EQ(0, store.size());

  // add observer 2, invoke, then remove
  EXPECT_EQ(0, store.size());
  EXPECT_TRUE(store.add(obs2));
  EXPECT_EQ(1, store.size());
  store.invokeForEachObserver(
      [](auto* observer) { observer->incrementCount(); },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
  EXPECT_EQ(1, obs1->getCount());
  EXPECT_EQ(1, obs2->getCount());

  EXPECT_TRUE(store.remove(obs2));
  EXPECT_EQ(0, store.size());
  store.invokeForEachObserver(
      [](auto* observer) { observer->incrementCount(); },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
  EXPECT_EQ(0, store.size());

  // add each observer and invoke, and then do the reverse
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  store.invokeForEachObserver(
      [](auto* observer) { observer->incrementCount(); },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
  EXPECT_EQ(2, obs1->getCount());
  EXPECT_EQ(1, obs2->getCount());

  EXPECT_TRUE(store.add(obs2));
  EXPECT_EQ(2, store.size());
  store.invokeForEachObserver(
      [](auto* observer) { observer->incrementCount(); },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
  EXPECT_EQ(3, obs1->getCount());
  EXPECT_EQ(2, obs2->getCount());

  EXPECT_TRUE(store.remove(obs2));
  EXPECT_EQ(1, store.size());
  store.invokeForEachObserver(
      [](auto* observer) { observer->incrementCount(); },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
  EXPECT_EQ(4, obs1->getCount());
  EXPECT_EQ(2, obs2->getCount());

  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(0, store.size());
  store.invokeForEachObserver(
      [](auto* observer) { observer->incrementCount(); },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
}

TEST_F(ObserverContainerStoreTest, AddRemoveDuplicateAdd) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();

  EXPECT_EQ(0, store.size());
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  EXPECT_FALSE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(0, store.size());
}

TEST_F(ObserverContainerStoreTest, AddRemoveDuplicateRemove) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();

  EXPECT_EQ(0, store.size());
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(0, store.size());
  EXPECT_FALSE(store.remove(obs1));
  EXPECT_EQ(0, store.size());
}

TEST_F(ObserverContainerStoreTest, AddWhileIteratingPolicyInvokeAdded) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();
  auto obs2 = std::make_shared<TestStoreObserver>();
  EXPECT_EQ(0, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());
  EXPECT_EQ(0, obs1->getCount()); // sanity check no side effects on getCount()
  EXPECT_EQ(0, obs2->getCount()); // sanity check no side effects on getCount()

  // add observer1, then during invoke, try to add observer2
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  store.invokeForEachObserver(
      [&obs1, &obs2, &store](auto* observer) {
        if (observer == obs1.get()) {
          EXPECT_TRUE(store.add(obs2)); // add observer 2
        }
        observer->incrementCount();
      },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
  EXPECT_EQ(2, store.size());

  // both observers should have a count of 1
  EXPECT_EQ(1, obs1->getCount());
  EXPECT_EQ(1, obs2->getCount());

  // remove both observers
  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(1, store.size());
  EXPECT_TRUE(store.remove(obs2));
  EXPECT_EQ(0, store.size());
}

TEST_F(ObserverContainerStoreTest, AddWhileIteratingPolicyDoNotInvokeAdded) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();
  auto obs2 = std::make_shared<TestStoreObserver>();
  EXPECT_EQ(0, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());
  EXPECT_EQ(0, obs1->getCount()); // sanity check no side effects on getCount()
  EXPECT_EQ(0, obs2->getCount()); // sanity check no side effects on getCount()

  // add observer1, then during invoke, try to add observer2
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  store.invokeForEachObserver(
      [&obs1, &obs2, &store](auto* observer) {
        if (observer == obs1.get()) {
          EXPECT_TRUE(store.add(obs2)); // add observer 2
        }
        observer->incrementCount();
      },
      Store::InvokeWhileIteratingPolicy::DoNotInvokeAdded);
  EXPECT_EQ(2, store.size());

  // incrementCount should have only been invoked for observer1
  EXPECT_EQ(1, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());

  // remove both observers
  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(1, store.size());
  EXPECT_TRUE(store.remove(obs2));
  EXPECT_EQ(0, store.size());
}

TEST_F(ObserverContainerStoreTest, AddWhileIteratingPolicyCheckNoChange) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();
  auto obs2 = std::make_shared<TestStoreObserver>();

  // add observer1, then during invoke, try to add observer2
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  store.invokeForEachObserver(
      [&obs1, &obs2, &store](auto* observer) {
        if (observer == obs1.get()) {
          // adding observers during iteration isn't allowed; expect exit
          EXPECT_EXIT(store.add(obs2), testing::KilledBySignal(SIGABRT), ".*");
        }
        observer->incrementCount();
      },
      Store::InvokeWhileIteratingPolicy::CheckNoChange);
}

TEST_F(ObserverContainerStoreTest, AddWhileIteratingPolicyCheckNoAdded) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();
  auto obs2 = std::make_shared<TestStoreObserver>();

  // add observer1, then during invoke, try to add observer2
  EXPECT_TRUE(store.add(obs1));
  EXPECT_EQ(1, store.size());
  store.invokeForEachObserver(
      [&obs1, &obs2, &store](auto* observer) {
        if (observer == obs1.get()) {
          // adding observers during iteration isn't allowed; expect exit
          EXPECT_EXIT(store.add(obs2), testing::KilledBySignal(SIGABRT), ".*");
        }
        observer->incrementCount();
      },
      Store::InvokeWhileIteratingPolicy::CheckNoAdded);
}

TEST_F(ObserverContainerStoreTest, RemoveWhileIteratingPolicyInvokeAdded) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();
  auto obs2 = std::make_shared<TestStoreObserver>();
  EXPECT_EQ(0, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());
  EXPECT_EQ(0, obs1->getCount()); // sanity check no side effects on getCount()
  EXPECT_EQ(0, obs2->getCount()); // sanity check no side effects on getCount()

  // add observers 1 and 2, then during invoke, remove observer2
  EXPECT_TRUE(store.add(obs1));
  EXPECT_TRUE(store.add(obs2));
  EXPECT_EQ(2, store.size());
  store.invokeForEachObserver(
      [&obs1, &obs2, &store](auto* observer) {
        if (observer == obs1.get()) {
          EXPECT_TRUE(store.remove(obs2)); // remove observer 2
        }
        observer->incrementCount();
      },
      Store::InvokeWhileIteratingPolicy::InvokeAdded);
  EXPECT_EQ(1, store.size());

  // incrementCount should have only been invoked for observer1
  // observer2 would have been removed already
  EXPECT_EQ(1, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());

  // remove observer 1
  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(0, store.size());
}

TEST_F(ObserverContainerStoreTest, RemoveWhileIteratingPolicyDoNotInvokeAdded) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();
  auto obs2 = std::make_shared<TestStoreObserver>();
  EXPECT_EQ(0, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());
  EXPECT_EQ(0, obs1->getCount()); // sanity check no side effects on getCount()
  EXPECT_EQ(0, obs2->getCount()); // sanity check no side effects on getCount()

  // add observers 1 and 2, then during invoke, remove observer2
  EXPECT_TRUE(store.add(obs1));
  EXPECT_TRUE(store.add(obs2));
  EXPECT_EQ(2, store.size());
  store.invokeForEachObserver(
      [&obs1, &obs2, &store](auto* observer) {
        if (observer == obs1.get()) {
          EXPECT_TRUE(store.remove(obs2)); // remove observer 2
        }
        observer->incrementCount();
      },
      Store::InvokeWhileIteratingPolicy::DoNotInvokeAdded);
  EXPECT_EQ(1, store.size());

  // incrementCount should have only been invoked for observer1
  // observer2 would have been removed already
  EXPECT_EQ(1, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());

  // remove observer 1
  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(0, store.size());
}

TEST_F(ObserverContainerStoreTest, RemoveWhileIteratingPolicyCheckNoChange) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();
  auto obs2 = std::make_shared<TestStoreObserver>();
  EXPECT_EQ(0, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());
  EXPECT_EQ(0, obs1->getCount()); // sanity check no side effects on getCount()
  EXPECT_EQ(0, obs2->getCount()); // sanity check no side effects on getCount()

  // add observers 1 and 2, then during invoke, remove observer2
  EXPECT_TRUE(store.add(obs1));
  EXPECT_TRUE(store.add(obs2));
  EXPECT_EQ(2, store.size());
  store.invokeForEachObserver(
      [&obs1, &obs2, &store](auto* observer) {
        if (observer == obs1.get()) {
          // adding observers during iteration isn't allowed; expect exit
          EXPECT_EXIT(
              store.remove(obs2), testing::KilledBySignal(SIGABRT), ".*");
        }
        observer->incrementCount();
      },
      Store::InvokeWhileIteratingPolicy::CheckNoChange);
}

TEST_F(ObserverContainerStoreTest, RemoveWhileIteratingPolicyCheckNoAdded) {
  Store store;
  auto obs1 = std::make_shared<TestStoreObserver>();
  auto obs2 = std::make_shared<TestStoreObserver>();
  EXPECT_EQ(0, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());
  EXPECT_EQ(0, obs1->getCount()); // sanity check no side effects on getCount()
  EXPECT_EQ(0, obs2->getCount()); // sanity check no side effects on getCount()

  // add observers 1 and 2, then during invoke, remove observer2
  EXPECT_TRUE(store.add(obs1));
  EXPECT_TRUE(store.add(obs2));
  EXPECT_EQ(2, store.size());
  store.invokeForEachObserver(
      [&obs1, &obs2, &store](auto* observer) {
        if (observer == obs1.get()) {
          EXPECT_TRUE(store.remove(obs2)); // remove observer 2
        }
        observer->incrementCount();
      },
      Store::InvokeWhileIteratingPolicy::CheckNoAdded);
  EXPECT_EQ(1, store.size());

  // incrementCount should have only been invoked for observer1
  // observer2 would have been removed already
  EXPECT_EQ(1, obs1->getCount());
  EXPECT_EQ(0, obs2->getCount());

  // remove observer 1
  EXPECT_TRUE(store.remove(obs1));
  EXPECT_EQ(0, store.size());
}
