/*
 * Copyright 2026 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 <buildboxcommon_credentialhelperauthenticator.h>
#include <buildboxcommon_fileutils.h>
#include <buildboxcommon_temporarydirectory.h>

#include <gtest/gtest.h>

#include <chrono>
#include <fstream>
#include <stdexcept>
#include <thread>

using namespace buildboxcommon;

TEST(CredentialHelperAuthenticatorTest, Construction)
{
    EXPECT_NO_THROW(CredentialHelperAuthenticator auth("/usr/bin/helper"));
}

TEST(CredentialHelperAuthenticatorTest, EmptyConstruction)
{
    EXPECT_NO_THROW(CredentialHelperAuthenticator auth(""));
}

TEST(CredentialHelperAuthenticatorTest, IsNotBlocking)
{
    CredentialHelperAuthenticator auth("/usr/bin/helper");
    EXPECT_FALSE(auth.IsBlocking());
}

class DummyAuthContext final : public grpc::AuthContext {
  public:
    ~DummyAuthContext() override = default;

    bool IsPeerAuthenticated() const override { return false; }

    std::vector<grpc::string_ref> GetPeerIdentity() const override
    {
        return std::vector<grpc::string_ref>();
    }

    std::string GetPeerIdentityPropertyName() const override { return ""; }

    std::vector<grpc::string_ref>
    FindPropertyValues(const std::string &name) const override
    {
        return std::vector<grpc::string_ref>();
    }

    grpc::AuthPropertyIterator begin() const override { return end(); }

    grpc::AuthPropertyIterator end() const override
    {
        throw std::logic_error("Not implemented");
    }

    void AddProperty(const std::string &key,
                     const grpc::string_ref &value) override
    {
    }

    bool SetPeerIdentityPropertyName(const std::string &name) override
    {
        return false;
    }
};

// Test fixture for comprehensive credential helper tests
class CredentialHelperAuthenticatorFixture : public ::testing::Test {
  protected:
    TemporaryDirectory helperDir;

    // Helper function to create an executable script with given content
    std::string createHelper(const std::string &scriptContent)
    {
        std::string helperPath = helperDir.strname() + "/helper.sh";
        FileUtils::writeFileAtomically(helperPath, scriptContent);
        FileUtils::makeExecutable(helperPath.c_str());
        return helperPath;
    }

    // Helper function to invoke GetMetadata with proper grpc types
    grpc::Status
    invokeGetMetadata(CredentialHelperAuthenticator &auth,
                      const std::string &serviceUrl,
                      const std::string &methodName,
                      std::multimap<grpc::string, grpc::string> *metadata)
    {
        grpc::string_ref serviceUrlRef(serviceUrl.data(), serviceUrl.size());
        grpc::string_ref methodNameRef(methodName.data(), methodName.size());
        DummyAuthContext authContext;
        return auth.GetMetadata(serviceUrlRef, methodNameRef, authContext,
                                metadata);
    }

    // Helper function to generate future RFC 3339 timestamp
    std::string futureTimestamp(int secondsFromNow)
    {
        auto now = std::chrono::system_clock::now();
        auto future = now + std::chrono::seconds(secondsFromNow);
        auto time_t = std::chrono::system_clock::to_time_t(future);
        std::tm tm{};
        if (gmtime_r(&time_t, &tm) == NULL) {
            throw std::runtime_error("gmtime_r returned NULL");
        }
        std::array<char, 32> buf{};
        size_t written_data =
            strftime(buf.data(), sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", &tm);
        if (written_data == 0) {
            throw std::runtime_error("Output timestamp exceeds max buff size");
        }
        return std::string(buf.data());
    }

    // Helper function to generate past RFC 3339 timestamp
    std::string pastTimestamp() { return "2020-01-01T00:00:00Z"; }
};

TEST_F(CredentialHelperAuthenticatorFixture, SuccessWithValidHeaders)
{
    std::string script = R"(#!/bin/bash
cat <<'EOF'
{
  "headers": {
    "Authorization": ["Bearer token123"],
    "X-Custom-Header": ["value1", "value2"]
  }
}
EOF
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status = invokeGetMetadata(auth, "https://example.com",
                                            "/Service/Method", &metadata);

    EXPECT_TRUE(status.ok());
    EXPECT_EQ(metadata.count("authorization"), 1);
    EXPECT_EQ(metadata.count("x-custom-header"), 2);

    auto authRange = metadata.equal_range("authorization");
    EXPECT_EQ(authRange.first->second, "Bearer token123");

    auto customRange = metadata.equal_range("x-custom-header");
    std::vector<std::string> values;
    for (auto it = customRange.first; it != customRange.second; ++it) {
        values.push_back(it->second);
    }
    EXPECT_EQ(values.size(), 2);
    EXPECT_TRUE(std::find(values.begin(), values.end(), "value1") !=
                values.end());
    EXPECT_TRUE(std::find(values.begin(), values.end(), "value2") !=
                values.end());
}

TEST_F(CredentialHelperAuthenticatorFixture, SuccessWithExpiration)
{
    std::string counterFile = helperDir.strname() + "/counter.txt";
    std::string script = "#!/bin/bash\n"
                         "echo '1' >> " +
                         counterFile +
                         "\n"
                         "cat <<'EOF'\n"
                         "{\n"
                         "  \"headers\": {\"Auth\": [\"token\"]},\n"
                         "  \"expires\": \"" +
                         futureTimestamp(3600) +
                         "\"\n"
                         "}\n"
                         "EOF\n";

    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata1;
    grpc::Status status1 =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata1);
    EXPECT_TRUE(status1.ok());

    std::multimap<grpc::string, grpc::string> metadata2;
    grpc::Status status2 =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata2);
    EXPECT_TRUE(status2.ok());

    std::ifstream countFile(counterFile);
    long count = std::count(std::istreambuf_iterator<char>(countFile),
                            std::istreambuf_iterator<char>(), '\n');
    EXPECT_EQ(count, 1);
}

TEST_F(CredentialHelperAuthenticatorFixture, SuccessWithoutExpiration)
{
    std::string counterFile = helperDir.strname() + "/counter.txt";
    std::string script = "#!/bin/bash\n"
                         "echo '1' >> " +
                         counterFile +
                         "\n"
                         "cat <<'EOF'\n"
                         "{\n"
                         "  \"headers\": {\"Auth\": [\"token\"]}\n"
                         "}\n"
                         "EOF\n";

    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata1;
    invokeGetMetadata(auth, "https://example.com", "/Method", &metadata1);

    std::multimap<grpc::string, grpc::string> metadata2;
    invokeGetMetadata(auth, "https://example.com", "/Method", &metadata2);

    std::ifstream countFile(counterFile);
    long count = std::count(std::istreambuf_iterator<char>(countFile),
                            std::istreambuf_iterator<char>(), '\n');
    EXPECT_EQ(count, 2);
}

TEST_F(CredentialHelperAuthenticatorFixture, EmptyHelperPath)
{
    CredentialHelperAuthenticator auth("");

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_FALSE(status.ok());
    EXPECT_EQ(status.error_code(), grpc::StatusCode::UNAUTHENTICATED);
}

TEST_F(CredentialHelperAuthenticatorFixture, HeadersConvertedToLowercase)
{
    std::string script = R"(#!/bin/bash
cat <<'EOF'
{
  "headers": {
    "Authorization": ["Bearer token"],
    "X-Custom-Header": ["value"]
  }
}
EOF
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_TRUE(status.ok());
    EXPECT_EQ(metadata.count("authorization"), 1);
    EXPECT_EQ(metadata.count("x-custom-header"), 1);
    EXPECT_EQ(metadata.count("Authorization"), 0);
    EXPECT_EQ(metadata.count("X-Custom-Header"), 0);
}

TEST_F(CredentialHelperAuthenticatorFixture, MultipleValuesPerHeader)
{
    std::string script = R"(#!/bin/bash
cat <<'EOF'
{
  "headers": {
    "Cookie": ["session1=abc", "session2=def", "session3=ghi"]
  }
}
EOF
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_TRUE(status.ok());
    EXPECT_EQ(metadata.count("cookie"), 3);

    auto range = metadata.equal_range("cookie");
    std::vector<std::string> values;
    for (auto it = range.first; it != range.second; ++it) {
        values.push_back(it->second);
    }

    EXPECT_TRUE(std::find(values.begin(), values.end(), "session1=abc") !=
                values.end());
    EXPECT_TRUE(std::find(values.begin(), values.end(), "session2=def") !=
                values.end());
    EXPECT_TRUE(std::find(values.begin(), values.end(), "session3=ghi") !=
                values.end());
}

TEST_F(CredentialHelperAuthenticatorFixture, InvalidHeaderFormatString)
{
    std::string script = R"(#!/bin/bash
cat <<'EOF'
{
  "headers": {
    "InvalidHeader": "not-an-array",
    "ValidHeader": ["value"]
  }
}
EOF
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_TRUE(status.ok());
    EXPECT_EQ(metadata.count("invalidheader"), 0);
    EXPECT_EQ(metadata.count("validheader"), 1);
}

TEST_F(CredentialHelperAuthenticatorFixture, MixedValidInvalidHeaders)
{
    std::string script = R"(#!/bin/bash
cat <<'EOF'
{
  "headers": {
    "Header1": ["value1"],
    "Header2": "invalid",
    "Header3": ["value3"],
    "Header4": 123
  }
}
EOF
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_TRUE(status.ok());
    EXPECT_EQ(metadata.count("header1"), 1);
    EXPECT_EQ(metadata.count("header2"), 0);
    EXPECT_EQ(metadata.count("header3"), 1);
    EXPECT_EQ(metadata.count("header4"), 0);
}

TEST_F(CredentialHelperAuthenticatorFixture, MalformedJSON)
{
    std::string script = R"(#!/bin/bash
echo '{invalid json'
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_FALSE(status.ok());
    EXPECT_EQ(status.error_code(), grpc::StatusCode::UNAUTHENTICATED);
}

TEST_F(CredentialHelperAuthenticatorFixture, NonZeroExitCode)
{
    std::string script = R"(#!/bin/bash
exit 1
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_FALSE(status.ok());
    EXPECT_EQ(status.error_code(), grpc::StatusCode::UNAUTHENTICATED);
}

TEST_F(CredentialHelperAuthenticatorFixture, InvalidProtobufStructure)
{
    std::string script = R"(#!/bin/bash
cat <<'EOF'
{
  "headers": "should-be-struct-not-string"
}
EOF
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_FALSE(status.ok());
    EXPECT_EQ(status.error_code(), grpc::StatusCode::UNAUTHENTICATED);
}

TEST_F(CredentialHelperAuthenticatorFixture, HelperDoesNotExist)
{
    CredentialHelperAuthenticator auth("/nonexistent/path/helper");

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_FALSE(status.ok());
    EXPECT_EQ(status.error_code(), grpc::StatusCode::UNAUTHENTICATED);
}

TEST_F(CredentialHelperAuthenticatorFixture, HelperTimeout)
{
    // Use a busy loop that ignores signals to test timeout behavior
    std::string script = R"(#!/bin/bash
# Consume stdin first
cat > /dev/null
# Then enter an infinite loop
while true; do
    : # no-op
done
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    auto start = std::chrono::steady_clock::now();
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);
    auto end = std::chrono::steady_clock::now();
    auto duration =
        std::chrono::duration_cast<std::chrono::seconds>(end - start);

    EXPECT_FALSE(status.ok());
    EXPECT_EQ(status.error_code(), grpc::StatusCode::UNAUTHENTICATED);
    EXPECT_LE(duration.count(), 7);
}

TEST_F(CredentialHelperAuthenticatorFixture, CacheHit)
{
    std::string counterFile = helperDir.strname() + "/counter.txt";
    std::string script = "#!/bin/bash\n"
                         "echo '1' >> " +
                         counterFile +
                         "\n"
                         "cat <<'EOF'\n"
                         "{\n"
                         "  \"headers\": {\"Auth\": [\"token\"]},\n"
                         "  \"expires\": \"" +
                         futureTimestamp(3600) +
                         "\"\n"
                         "}\n"
                         "EOF\n";

    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata1;
    invokeGetMetadata(auth, "https://example.com", "/Method", &metadata1);

    std::multimap<grpc::string, grpc::string> metadata2;
    invokeGetMetadata(auth, "https://example.com", "/Method", &metadata2);

    std::ifstream countFile(counterFile);
    long count = std::count(std::istreambuf_iterator<char>(countFile),
                            std::istreambuf_iterator<char>(), '\n');
    EXPECT_EQ(count, 1);
}

TEST_F(CredentialHelperAuthenticatorFixture, CacheMissDifferentURI)
{
    std::string counterFile = helperDir.strname() + "/counter.txt";
    std::string script = "#!/bin/bash\n"
                         "echo '1' >> " +
                         counterFile +
                         "\n"
                         "cat <<'EOF'\n"
                         "{\n"
                         "  \"headers\": {\"Auth\": [\"token\"]},\n"
                         "  \"expires\": \"" +
                         futureTimestamp(3600) +
                         "\"\n"
                         "}\n"
                         "EOF\n";

    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata1;
    invokeGetMetadata(auth, "https://example.com", "/Method1", &metadata1);

    std::multimap<grpc::string, grpc::string> metadata2;
    invokeGetMetadata(auth, "https://example.com", "/Method2", &metadata2);

    std::ifstream countFile(counterFile);
    long count = std::count(std::istreambuf_iterator<char>(countFile),
                            std::istreambuf_iterator<char>(), '\n');
    EXPECT_EQ(count, 2);
}

TEST_F(CredentialHelperAuthenticatorFixture, CacheExpiration)
{
    std::string counterFile = helperDir.strname() + "/counter.txt";
    std::string script = "#!/bin/bash\n"
                         "echo '1' >> " +
                         counterFile +
                         "\n"
                         "cat <<'EOF'\n"
                         "{\n"
                         "  \"headers\": {\"Auth\": [\"token\"]},\n"
                         "  \"expires\": \"" +
                         futureTimestamp(1) +
                         "\"\n"
                         "}\n"
                         "EOF\n";

    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata1;
    invokeGetMetadata(auth, "https://example.com", "/Method", &metadata1);

    std::multimap<grpc::string, grpc::string> metadata2;
    invokeGetMetadata(auth, "https://example.com", "/Method", &metadata2);

    std::this_thread::sleep_for(std::chrono::seconds(2));

    std::multimap<grpc::string, grpc::string> metadata3;
    invokeGetMetadata(auth, "https://example.com", "/Method", &metadata3);

    std::ifstream countFile(counterFile);
    long count = std::count(std::istreambuf_iterator<char>(countFile),
                            std::istreambuf_iterator<char>(), '\n');
    EXPECT_EQ(count, 2);
}

TEST_F(CredentialHelperAuthenticatorFixture, AlreadyExpiredCredentials)
{
    std::string counterFile = helperDir.strname() + "/counter.txt";
    std::string script = "#!/bin/bash\n"
                         "echo '1' >> " +
                         counterFile +
                         "\n"
                         "cat <<'EOF'\n"
                         "{\n"
                         "  \"headers\": {\"Auth\": [\"token\"]},\n"
                         "  \"expires\": \"" +
                         pastTimestamp() +
                         "\"\n"
                         "}\n"
                         "EOF\n";

    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata1;
    grpc::Status status1 =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata1);
    EXPECT_TRUE(status1.ok());

    std::multimap<grpc::string, grpc::string> metadata2;
    grpc::Status status2 =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata2);
    EXPECT_TRUE(status2.ok());

    std::ifstream countFile(counterFile);
    long count = std::count(std::istreambuf_iterator<char>(countFile),
                            std::istreambuf_iterator<char>(), '\n');
    EXPECT_EQ(count, 2);
}

TEST_F(CredentialHelperAuthenticatorFixture, NoExpirationNotCached)
{
    std::string counterFile = helperDir.strname() + "/counter.txt";
    std::string script = "#!/bin/bash\n"
                         "echo '1' >> " +
                         counterFile +
                         "\n"
                         "cat <<'EOF'\n"
                         "{\n"
                         "  \"headers\": {\"Auth\": [\"token\"]}\n"
                         "}\n"
                         "EOF\n";

    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    for (int i = 0; i < 3; i++) {
        std::multimap<grpc::string, grpc::string> metadata;
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);
    }

    std::ifstream countFile(counterFile);
    long count = std::count(std::istreambuf_iterator<char>(countFile),
                            std::istreambuf_iterator<char>(), '\n');
    EXPECT_EQ(count, 3);
}

TEST_F(CredentialHelperAuthenticatorFixture, EmptyHeaders)
{
    std::string script = R"(#!/bin/bash
cat <<'EOF'
{
  "headers": {}
}
EOF
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_TRUE(status.ok());
    EXPECT_EQ(metadata.size(), 0);
}

TEST_F(CredentialHelperAuthenticatorFixture, ConcurrentCacheMiss)
{
    std::string script = R"(#!/bin/bash
read -r INPUT
URI=$(echo "$INPUT" | grep -o '"uri":"[^"]*"' | cut -d'"' -f4)
cat <<EOF
{
  "headers": {"Auth": ["token-$URI"]},
  "expires": "2026-12-31T23:59:59Z"
}
EOF
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::vector<std::thread> threads;
    std::vector<grpc::Status> results(10);
    std::vector<std::multimap<grpc::string, grpc::string>> metadatas(10);
    threads.reserve(10);

    for (int i = 0; i < 10; i++) {
        threads.emplace_back([&, i]() {
            std::string uri = "https://example.com/Method" + std::to_string(i);
            results[i] = invokeGetMetadata(auth, uri, "", &metadatas[i]);
        });
    }

    for (auto &t : threads)
        t.join();

    for (const auto &status : results) {
        EXPECT_TRUE(status.ok());
    }
}

TEST_F(CredentialHelperAuthenticatorFixture, ConcurrentCacheHit)
{
    std::string script = "#!/bin/bash\n"
                         "cat <<'EOF'\n"
                         "{\n"
                         "  \"headers\": {\"Auth\": [\"token\"]},\n"
                         "  \"expires\": \"" +
                         futureTimestamp(3600) +
                         "\"\n"
                         "}\n"
                         "EOF\n";

    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> warmupMetadata;
    invokeGetMetadata(auth, "https://example.com", "/Method", &warmupMetadata);

    std::vector<std::thread> threads;
    std::vector<grpc::Status> results(10);
    std::vector<std::multimap<grpc::string, grpc::string>> metadatas(10);
    threads.reserve(10);

    for (int i = 0; i < 10; i++) {
        threads.emplace_back([&, i]() {
            results[i] = invokeGetMetadata(auth, "https://example.com",
                                           "/Method", &metadatas[i]);
        });
    }

    for (auto &t : threads)
        t.join();

    for (const auto &status : results) {
        EXPECT_TRUE(status.ok());
    }
    for (const auto &metadata : metadatas) {
        EXPECT_EQ(metadata.count("auth"), 1);
    }
}

TEST_F(CredentialHelperAuthenticatorFixture, EmptyHeaderArrayValue)
{
    std::string script = R"(#!/bin/bash
cat <<'EOF'
{
  "headers": {
    "EmptyHeader": [],
    "ValidHeader": ["value"]
  }
}
EOF
)";
    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_TRUE(status.ok());
    EXPECT_EQ(metadata.count("emptyheader"), 0);
    EXPECT_EQ(metadata.count("validheader"), 1);
}

TEST_F(CredentialHelperAuthenticatorFixture, VeryLongHeaderValue)
{
    std::string longValue(10000, 'x');
    std::string script = "#!/bin/bash\n"
                         "cat <<'EOF'\n"
                         "{\n"
                         "  \"headers\": {\"LongHeader\": [\"" +
                         longValue +
                         "\"]}\n"
                         "}\n"
                         "EOF\n";

    std::string helperPath = createHelper(script);
    CredentialHelperAuthenticator auth(helperPath);

    std::multimap<grpc::string, grpc::string> metadata;
    grpc::Status status =
        invokeGetMetadata(auth, "https://example.com", "/Method", &metadata);

    EXPECT_TRUE(status.ok());
    EXPECT_EQ(metadata.count("longheader"), 1);
    auto range = metadata.equal_range("longheader");
    EXPECT_EQ(range.first->second.size(), 10000);
}
