/*
 * Copyright 2020 Bloomberg Finance LP
 *
 * 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 <logstreamtail.h>

#include <buildboxcommon_protos.h>

#include <google/bytestream/bytestream.grpc.pb.h>
#include <google/bytestream/bytestream_mock.grpc.pb.h>
#include <grpcpp/test/mock_stream.h>
#include <gtest/gtest.h>

#include <memory>

using namespace buildboxcommon;
using namespace testing;

class LogStreamTailFixture : public ::testing::Test {
  protected:
    typedef grpc::testing::MockClientReader<google::bytestream::ReadResponse>
        MockClientReader;

    LogStreamTailFixture()
        : byteStreamClient(
              std::make_shared<google::bytestream::MockByteStreamStub>()),
          reader(new MockClientReader())
    {
    }

    std::shared_ptr<google::bytestream::MockByteStreamStub> byteStreamClient;

    MockClientReader *reader;
    // Wrapping this in a smart pointer causes issues on destruction.
    // (It is freed by someone else.)
};

TEST_F(LogStreamTailFixture, TestSuccessfulRead)
{
    buildboxcommon::ReadRequest request;
    EXPECT_CALL(*byteStreamClient, ReadRaw(_, _))
        .WillOnce(DoAll(SaveArg<1>(&request), Return(reader)));

    // Sending the data in chunks:
    buildboxcommon::ReadResponse response1;
    response1.set_data("Hello, ");

    buildboxcommon::ReadResponse response2;
    response2.set_data("world.\n");

    buildboxcommon::ReadResponse response3;
    response3.set_data("Bye!");

    EXPECT_CALL(*reader, Read(_))
        .WillOnce(DoAll(SetArgPointee<0>(response1), Return(true)))
        .WillOnce(DoAll(SetArgPointee<0>(response2), Return(true)))
        .WillOnce(DoAll(SetArgPointee<0>(response3), Return(true)))
        .WillOnce(Return(false));
    EXPECT_CALL(*reader, Finish()).WillOnce(Return(grpc::Status::OK));

    // Gathering all the data that is read in a single string:
    std::string dataReceived;
    const auto write = [&dataReceived](const std::string &d) {
        dataReceived += d;
    };

    const std::string resourceName = "log-stream-test";
    const auto status =
        LogStreamTail::readLogStream(byteStreamClient, resourceName, write);

    ASSERT_TRUE(status.ok());

    ASSERT_EQ(request.read_offset(), 0);
    ASSERT_EQ(dataReceived, "Hello, world.\nBye!");
}

TEST_F(LogStreamTailFixture, TestServerError)
{
    // Gathering all the data that is read in a single string:
    const auto dummyCallback = [&](const std::string &) { return; };

    EXPECT_CALL(*byteStreamClient, ReadRaw(_, _)).WillOnce(Return(reader));
    EXPECT_CALL(*reader, Read(_)).WillOnce(Return(false));

    const grpc::Status errorStatus(grpc::StatusCode::INTERNAL, "Server Error");
    EXPECT_CALL(*reader, Finish()).WillOnce(Return(errorStatus));

    const auto status = LogStreamTail::readLogStream(
        byteStreamClient, "resource-name", dummyCallback);

    ASSERT_EQ(status.error_code(), errorStatus.error_code());
    ASSERT_EQ(status.error_message(), errorStatus.error_message());
}
