Add prefix for Azure Core Tests - Fix CI test run (#801)
* Add prefix for Azure Core Tests
This commit is contained in:
parent
8f1ab29a6d
commit
d79d95e5b7
@ -87,7 +87,7 @@ jobs:
|
||||
parameters:
|
||||
GenerateArgs: $(CmakeArgs)
|
||||
|
||||
- script: ctest -V --tests-regex ${{ parameters.CtestRegex }}
|
||||
- script: ctest -C Debug -V --tests-regex ${{ parameters.CtestRegex }}
|
||||
workingDirectory: build
|
||||
displayName: ctest
|
||||
|
||||
|
||||
@ -25,8 +25,7 @@
|
||||
#ifdef TESTING_BUILD
|
||||
// Define the class name that reads from ConnectionPool private members
|
||||
namespace Azure { namespace Core { namespace Test {
|
||||
class TransportAdapter_ConnectionPoolCleaner_Test;
|
||||
class TransportAdapter_getMultiThread_Test;
|
||||
class TransportAdapter_connectionPoolTest_Test;
|
||||
}}} // namespace Azure::Core::Test
|
||||
#endif
|
||||
|
||||
@ -110,8 +109,7 @@ namespace Azure { namespace Core { namespace Http {
|
||||
{
|
||||
#ifdef TESTING_BUILD
|
||||
// Give access to private to this tests class
|
||||
friend class Azure::Core::Test::TransportAdapter_getMultiThread_Test;
|
||||
friend class Azure::Core::Test::TransportAdapter_ConnectionPoolCleaner_Test;
|
||||
friend class Azure::Core::Test::TransportAdapter_connectionPoolTest_Test;
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@ -39,5 +39,6 @@ target_link_libraries(${TARGET_NAME} PRIVATE azure-core gtest)
|
||||
|
||||
# gtest_add_tests will scan the test from azure-core-test and call add_test
|
||||
# for each test to ctest. This enables `ctest -r` to run specific tests directly.
|
||||
gtest_add_tests(TARGET ${TARGET_NAME})
|
||||
gtest_add_tests(TARGET ${TARGET_NAME}
|
||||
TEST_PREFIX azure-core.)
|
||||
|
||||
|
||||
@ -30,461 +30,454 @@ namespace Azure { namespace Core { namespace Test {
|
||||
Azure::Core::Http::HttpPipeline TransportAdapter::pipeline(policies);
|
||||
Azure::Core::Context TransportAdapter::context = Azure::Core::GetApplicationContext();
|
||||
|
||||
// multiThread test requires `ConnectionsOnPool` hook which is only available when building
|
||||
// TESTING_BUILD. This test is only built when that case is true.
|
||||
TEST_F(TransportAdapter, getMultiThread)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
Azure::Core::Http::CurlConnectionPool::ClearIndex();
|
||||
// Connection pool feature is curl-implementation only. No other transport adapter would have the
|
||||
// connection pool
|
||||
#ifdef BUILD_CURL_HTTP_TRANSPORT_ADAPTER
|
||||
// connectionPoolTest requires `ConnectionsOnPool` hook which is only available when building
|
||||
// BUILD_TESTING. This test is only built when that case is true.
|
||||
TEST_F(TransportAdapter, connectionPoolTest)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
Azure::Core::Http::CurlConnectionPool::ClearIndex();
|
||||
|
||||
auto threadRoutine = [host]() {
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
};
|
||||
auto threadRoutine = [host]() {
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
};
|
||||
|
||||
std::thread t1(threadRoutine);
|
||||
std::thread t2(threadRoutine);
|
||||
t1.join();
|
||||
t2.join();
|
||||
std::thread t1(threadRoutine);
|
||||
std::thread t2(threadRoutine);
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
// 2 connections must be available at this point
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 2);
|
||||
// 2 connections must be available at this point
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 2);
|
||||
|
||||
std::thread t3(threadRoutine);
|
||||
std::thread t4(threadRoutine);
|
||||
std::thread t5(threadRoutine);
|
||||
t3.join();
|
||||
t4.join();
|
||||
t5.join();
|
||||
std::thread t3(threadRoutine);
|
||||
std::thread t4(threadRoutine);
|
||||
std::thread t5(threadRoutine);
|
||||
t3.join();
|
||||
t4.join();
|
||||
t5.join();
|
||||
|
||||
// Two connections re-used plus one connection created
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 3);
|
||||
}
|
||||
// Two connections re-used plus one connection created
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 3);
|
||||
|
||||
#ifdef RUN_LONG_UNIT_TESTS
|
||||
TEST_F(TransportAdapter, ConnectionPoolCleaner)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
{
|
||||
// Test pool clean routine
|
||||
std::cout << "Running Connection Pool Cleaner Test. This test takes more than 3 minutes to "
|
||||
"complete."
|
||||
<< std::endl
|
||||
<< "Add compiler option -DRUN_LONG_UNIT_TESTS=OFF when building if you want to "
|
||||
"skip this "
|
||||
"test."
|
||||
<< std::endl;
|
||||
|
||||
// Wait for 180 secs to make sure any previous connection is removed by the cleaner
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000 * 180));
|
||||
|
||||
std::cout << "First wait time done. Validating state." << std::endl;
|
||||
|
||||
// index is not affected by cleaner. It does not remove index
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsIndexOnPool(), 1);
|
||||
// cleaner should have removed connections
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 0);
|
||||
|
||||
std::thread t1(threadRoutine);
|
||||
std::thread t2(threadRoutine);
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
// wait for connection to be moved back to pool
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
// 2 connections must be available at this point and one index
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsIndexOnPool(), 1);
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 2);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(TransportAdapter, get)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
auto threadRoutine = [host]() {
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
};
|
||||
|
||||
// 3 connections from previous test. Make sure cleaner remove them
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 3);
|
||||
|
||||
std::cout
|
||||
<< "Running Connection Pool Cleaner Test. This test takes more than 3 minutes to complete."
|
||||
<< std::endl
|
||||
<< "Add compiler option -DRUN_LONG_UNIT_TESTS=OFF when building if you want to skip this "
|
||||
"test."
|
||||
<< std::endl;
|
||||
|
||||
// Wait for 180 secs to make sure any previous connection is removed by the cleaner
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000 * 180));
|
||||
|
||||
std::cout << "First wait time done. Validating state." << std::endl;
|
||||
|
||||
// index is not affected by cleaner. It does not remove index
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsIndexOnPool(), 1);
|
||||
// cleaner should have remove connections
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 0);
|
||||
|
||||
std::thread t1(threadRoutine);
|
||||
std::thread t2(threadRoutine);
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
// wait for connection to be moved back to pool
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
|
||||
|
||||
// 2 connections must be available at this point and one index
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsIndexOnPool(), 1);
|
||||
EXPECT_EQ(Http::CurlConnectionPool::ConnectionsOnPool("httpbin.org"), 2);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(TransportAdapter, get)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
|
||||
// Need to init request again, since retry would be on after it is sent
|
||||
request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
// Add a header and send again. RawResponse should return that header in the body
|
||||
request.AddHeader("123", "456");
|
||||
response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
// header length is 6 (data) + 13 (formating) -> ` "123": "456"\r\n,`
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize + 6 + 13);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, get204)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://mt3.google.com/generate_204");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::NoContent);
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, getLoop)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
|
||||
// loop sending request
|
||||
for (auto i = 0; i < 50; i++)
|
||||
{
|
||||
auto response = pipeline.Send(context, request);
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
// Need to init request again, since retry would be on after it is sent
|
||||
request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
// Add a header and send again. RawResponse should return that header in the body
|
||||
request.AddHeader("123", "456");
|
||||
response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
// header length is 6 (data) + 13 (formating) -> ` "123": "456"\r\n,`
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize + 6 + 13);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, get204)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://mt3.google.com/generate_204");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::NoContent);
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, head)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
auto expectedResponseBodySize = 0;
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
|
||||
// Check content-length header to be greater than 0
|
||||
int64_t contentLengthHeader = std::stoull(response->GetHeaders().at("content-length"));
|
||||
EXPECT_TRUE(contentLengthHeader > 0);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, put)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/put");
|
||||
|
||||
// PUT 1K
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, deleteRequest)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/delete");
|
||||
|
||||
// Delete with 1k payload
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Delete, host, &bodyRequest);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, patch)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/patch");
|
||||
|
||||
// Patch with 1kb payload
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Patch, host, &bodyRequest);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, getChunk)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://anglesharp.azurewebsites.net/Chunked");
|
||||
auto expectedResponseBodySize = -1; // chunked will return unknown body length
|
||||
auto expectedChunkResponse = std::string(
|
||||
"<!DOCTYPE html>\r\n<html lang=en>\r\n<head>\r\n<meta charset='utf-8'>\r\n<title>Chunked "
|
||||
"transfer encoding test</title>\r\n</head>\r\n<body><h1>Chunked transfer encoding "
|
||||
"test</h1><h5>This is a chunked response after 100 ms.</h5><h5>This is a chunked "
|
||||
"response after 1 second. The server should not close the stream before all chunks are "
|
||||
"sent to a client.</h5></body></html>");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize, expectedChunkResponse);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, putErrorResponse)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
// Try to make a PUT to a GET url. This will return an error code from server.
|
||||
// This test makes sure that the connection is not re-used (because it gets closed by server)
|
||||
// and next request is not hang
|
||||
for (auto i = 0; i < 10; i++)
|
||||
TEST_F(TransportAdapter, getLoop)
|
||||
{
|
||||
auto requestBodyVector = std::vector<uint8_t>(10, 'x');
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
|
||||
// loop sending request
|
||||
for (auto i = 0; i < 50; i++)
|
||||
{
|
||||
auto response = pipeline.Send(context, request);
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, head)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
auto expectedResponseBodySize = 0;
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
|
||||
// Check content-length header to be greater than 0
|
||||
int64_t contentLengthHeader = std::stoull(response->GetHeaders().at("content-length"));
|
||||
EXPECT_TRUE(contentLengthHeader > 0);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, put)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/put");
|
||||
|
||||
// PUT 1K
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest);
|
||||
auto response = pipeline.Send(context, request);
|
||||
}
|
||||
}
|
||||
|
||||
// **********************
|
||||
// ***Same tests but getting stream to pull from socket, simulating the Download Op
|
||||
// **********************
|
||||
|
||||
TEST_F(TransportAdapter, getWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
|
||||
request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true);
|
||||
// Add a header and send again. Response should return that header in the body
|
||||
request.AddHeader("123", "456");
|
||||
response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
// header length is 6 (data) + 13 (formating) -> ` "123": "456"\r\n,`
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize + 6 + 13);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, getLoopWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true);
|
||||
|
||||
// loop sending request
|
||||
for (auto i = 0; i < 50; i++)
|
||||
{
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, headWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
auto expectedResponseBodySize = 0;
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, host, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
|
||||
// Check content-length header to be greater than 0
|
||||
int64_t contentLengthHeader = std::stoull(response->GetHeaders().at("content-length"));
|
||||
EXPECT_TRUE(contentLengthHeader > 0);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, putWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/put");
|
||||
|
||||
// PUT 1k
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, deleteRequestWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/delete");
|
||||
|
||||
// Delete with 1k payload
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request = Azure::Core::Http::Request(
|
||||
Azure::Core::Http::HttpMethod::Delete, host, &bodyRequest, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, patchWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/patch");
|
||||
|
||||
// Patch with 1kb payload
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request = Azure::Core::Http::Request(
|
||||
Azure::Core::Http::HttpMethod::Patch, host, &bodyRequest, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, getChunkWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://anglesharp.azurewebsites.net/Chunked");
|
||||
auto expectedResponseBodySize = -1; // chunked will return unknown body length
|
||||
auto expectedChunkResponse = std::string(
|
||||
"<!DOCTYPE html>\r\n<html lang=en>\r\n<head>\r\n<meta charset='utf-8'>\r\n<title>Chunked "
|
||||
"transfer encoding test</title>\r\n</head>\r\n<body><h1>Chunked transfer encoding "
|
||||
"test</h1><h5>This is a chunked response after 100 ms.</h5><h5>This is a chunked "
|
||||
"response after 1 second. The server should not close the stream before all chunks are "
|
||||
"sent to a client.</h5></body></html>");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize, expectedChunkResponse);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, createResponseT)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
std::string expectedType("This is the Response Type");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, false);
|
||||
auto response = pipeline.Send(context, request);
|
||||
|
||||
Azure::Core::Response<std::string> responseT(expectedType, std::move(response));
|
||||
auto& r = responseT.GetRawResponse();
|
||||
|
||||
EXPECT_TRUE(r.GetStatusCode() == Azure::Core::Http::HttpStatusCode::Ok);
|
||||
auto expectedResponseBodySize = std::stoull(r.GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(r, expectedResponseBodySize);
|
||||
|
||||
// Direct access
|
||||
EXPECT_STREQ((*responseT).data(), expectedType.data());
|
||||
EXPECT_STREQ(responseT->data(), expectedType.data());
|
||||
// extracting T out of response
|
||||
auto result = responseT.ExtractValue();
|
||||
EXPECT_STREQ(result.data(), expectedType.data());
|
||||
// Test that calling getValue again will return empty
|
||||
result = responseT.ExtractValue();
|
||||
EXPECT_STREQ(result.data(), std::string("").data());
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, customSizePut)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/put");
|
||||
|
||||
// PUT 1MB
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024 * 1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest);
|
||||
// Make transport adapter to read all stream content for uploading instead of chunks
|
||||
request.SetUploadChunkSize(1024 * 1024);
|
||||
TEST_F(TransportAdapter, deleteRequest)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/delete");
|
||||
|
||||
// Delete with 1k payload
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Delete, host, &bodyRequest);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, putWithStreamOnFail)
|
||||
{
|
||||
// point to bad address pah to generate server MethodNotAllowed error
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
TEST_F(TransportAdapter, patch)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/patch");
|
||||
|
||||
// PUT 1k
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(
|
||||
response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::MethodNotAllowed);
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
// Patch with 1kb payload
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Patch, host, &bodyRequest);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, cancelTransferUpload)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/put");
|
||||
Azure::Core::Context cancelThis;
|
||||
TEST_F(TransportAdapter, getChunk)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://anglesharp.azurewebsites.net/Chunked");
|
||||
auto expectedResponseBodySize = -1; // chunked will return unknown body length
|
||||
auto expectedChunkResponse = std::string(
|
||||
"<!DOCTYPE html>\r\n<html lang=en>\r\n<head>\r\n<meta charset='utf-8'>\r\n<title>Chunked "
|
||||
"transfer encoding test</title>\r\n</head>\r\n<body><h1>Chunked transfer encoding "
|
||||
"test</h1><h5>This is a chunked response after 100 ms.</h5><h5>This is a chunked "
|
||||
"response after 1 second. The server should not close the stream before all chunks are "
|
||||
"sent to a client.</h5></body></html>");
|
||||
|
||||
auto threadRoutine = [host, cancelThis]() {
|
||||
// Start a big upload and expect it to throw cancelation
|
||||
std::vector<uint8_t> bigBuffer(1024 * 1024 * 200, 'x'); // upload 200 Mb
|
||||
auto stream = Azure::Core::Http::MemoryBodyStream(bigBuffer);
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &stream);
|
||||
|
||||
// Request will be canceled from main thread throwing the exception
|
||||
EXPECT_THROW(pipeline.Send(cancelThis, request), Azure::Core::RequestCanceledException);
|
||||
};
|
||||
|
||||
// Start request
|
||||
std::thread t1(threadRoutine);
|
||||
|
||||
// Wait 100 ms so we know upload has started
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
cancelThis.Cancel();
|
||||
t1.join();
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, cancelTransferDownload)
|
||||
{
|
||||
// public big blob (321MB)
|
||||
Azure::Core::Http::Url host("https://bigtestfiles.blob.core.windows.net/cpptestfiles/321MB");
|
||||
Azure::Core::Context cancelThis;
|
||||
|
||||
auto threadRoutine = [host, cancelThis]() {
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
|
||||
// Request will be canceled from main thread throwing the exception
|
||||
EXPECT_THROW(pipeline.Send(cancelThis, request), Azure::Core::RequestCanceledException);
|
||||
};
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize, expectedChunkResponse);
|
||||
}
|
||||
|
||||
// Start request
|
||||
std::thread t1(threadRoutine);
|
||||
TEST_F(TransportAdapter, putErrorResponse)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
// Wait 100 ms so we know download has started
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
// Try to make a PUT to a GET url. This will return an error code from server.
|
||||
// This test makes sure that the connection is not re-used (because it gets closed by server)
|
||||
// and next request is not hang
|
||||
for (auto i = 0; i < 10; i++)
|
||||
{
|
||||
auto requestBodyVector = std::vector<uint8_t>(10, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest);
|
||||
auto response = pipeline.Send(context, request);
|
||||
}
|
||||
}
|
||||
|
||||
cancelThis.Cancel();
|
||||
t1.join();
|
||||
}
|
||||
// **********************
|
||||
// ***Same tests but getting stream to pull from socket, simulating the Download Op
|
||||
// **********************
|
||||
|
||||
TEST_F(TransportAdapter, getWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
|
||||
request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true);
|
||||
// Add a header and send again. Response should return that header in the body
|
||||
request.AddHeader("123", "456");
|
||||
response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
// header length is 6 (data) + 13 (formating) -> ` "123": "456"\r\n,`
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize + 6 + 13);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, getLoopWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true);
|
||||
|
||||
// loop sending request
|
||||
for (auto i = 0; i < 50; i++)
|
||||
{
|
||||
auto response = pipeline.Send(context, request);
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, headWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
auto expectedResponseBodySize = 0;
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Head, host, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
|
||||
// Check content-length header to be greater than 0
|
||||
int64_t contentLengthHeader = std::stoull(response->GetHeaders().at("content-length"));
|
||||
EXPECT_TRUE(contentLengthHeader > 0);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, putWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/put");
|
||||
|
||||
// PUT 1k
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request = Azure::Core::Http::Request(
|
||||
Azure::Core::Http::HttpMethod::Put, host, &bodyRequest, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, deleteRequestWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/delete");
|
||||
|
||||
// Delete with 1k payload
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request = Azure::Core::Http::Request(
|
||||
Azure::Core::Http::HttpMethod::Delete, host, &bodyRequest, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, patchWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/patch");
|
||||
|
||||
// Patch with 1kb payload
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request = Azure::Core::Http::Request(
|
||||
Azure::Core::Http::HttpMethod::Patch, host, &bodyRequest, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, getChunkWithStream)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://anglesharp.azurewebsites.net/Chunked");
|
||||
auto expectedResponseBodySize = -1; // chunked will return unknown body length
|
||||
auto expectedChunkResponse = std::string(
|
||||
"<!DOCTYPE html>\r\n<html lang=en>\r\n<head>\r\n<meta charset='utf-8'>\r\n<title>Chunked "
|
||||
"transfer encoding test</title>\r\n</head>\r\n<body><h1>Chunked transfer encoding "
|
||||
"test</h1><h5>This is a chunked response after 100 ms.</h5><h5>This is a chunked "
|
||||
"response after 1 second. The server should not close the stream before all chunks are "
|
||||
"sent to a client.</h5></body></html>");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
CheckBodyFromStream(*response, expectedResponseBodySize, expectedChunkResponse);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, createResponseT)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
std::string expectedType("This is the Response Type");
|
||||
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, false);
|
||||
auto response = pipeline.Send(context, request);
|
||||
|
||||
Azure::Core::Response<std::string> responseT(expectedType, std::move(response));
|
||||
auto& r = responseT.GetRawResponse();
|
||||
|
||||
EXPECT_TRUE(r.GetStatusCode() == Azure::Core::Http::HttpStatusCode::Ok);
|
||||
auto expectedResponseBodySize = std::stoull(r.GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(r, expectedResponseBodySize);
|
||||
|
||||
// Direct access
|
||||
EXPECT_STREQ((*responseT).data(), expectedType.data());
|
||||
EXPECT_STREQ(responseT->data(), expectedType.data());
|
||||
// extracting T out of response
|
||||
auto result = responseT.ExtractValue();
|
||||
EXPECT_STREQ(result.data(), expectedType.data());
|
||||
// Test that calling getValue again will return empty
|
||||
result = responseT.ExtractValue();
|
||||
EXPECT_STREQ(result.data(), std::string("").data());
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, customSizePut)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/put");
|
||||
|
||||
// PUT 1MB
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024 * 1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &bodyRequest);
|
||||
// Make transport adapter to read all stream content for uploading instead of chunks
|
||||
request.SetUploadChunkSize(1024 * 1024);
|
||||
{
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, putWithStreamOnFail)
|
||||
{
|
||||
// point to bad address pah to generate server MethodNotAllowed error
|
||||
Azure::Core::Http::Url host("http://httpbin.org/get");
|
||||
|
||||
// PUT 1k
|
||||
auto requestBodyVector = std::vector<uint8_t>(1024, 'x');
|
||||
auto bodyRequest = Azure::Core::Http::MemoryBodyStream(requestBodyVector);
|
||||
auto request = Azure::Core::Http::Request(
|
||||
Azure::Core::Http::HttpMethod::Put, host, &bodyRequest, true);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(
|
||||
response->GetStatusCode(), Azure::Core::Http::HttpStatusCode::MethodNotAllowed);
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, cancelTransferUpload)
|
||||
{
|
||||
Azure::Core::Http::Url host("http://httpbin.org/put");
|
||||
Azure::Core::Context cancelThis;
|
||||
|
||||
auto threadRoutine = [host, cancelThis]() {
|
||||
// Start a big upload and expect it to throw cancelation
|
||||
std::vector<uint8_t> bigBuffer(1024 * 1024 * 200, 'x'); // upload 200 Mb
|
||||
auto stream = Azure::Core::Http::MemoryBodyStream(bigBuffer);
|
||||
auto request
|
||||
= Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Put, host, &stream);
|
||||
|
||||
// Request will be canceled from main thread throwing the exception
|
||||
EXPECT_THROW(pipeline.Send(cancelThis, request), Azure::Core::RequestCanceledException);
|
||||
};
|
||||
|
||||
// Start request
|
||||
std::thread t1(threadRoutine);
|
||||
|
||||
// Wait 100 ms so we know upload has started
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
cancelThis.Cancel();
|
||||
t1.join();
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, cancelTransferDownload)
|
||||
{
|
||||
// public big blob (321MB)
|
||||
Azure::Core::Http::Url host("https://bigtestfiles.blob.core.windows.net/cpptestfiles/321MB");
|
||||
Azure::Core::Context cancelThis;
|
||||
|
||||
auto threadRoutine = [host, cancelThis]() {
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
|
||||
// Request will be canceled from main thread throwing the exception
|
||||
EXPECT_THROW(pipeline.Send(cancelThis, request), Azure::Core::RequestCanceledException);
|
||||
};
|
||||
|
||||
// Start request
|
||||
std::thread t1(threadRoutine);
|
||||
|
||||
// Wait 100 ms so we know download has started
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
cancelThis.Cancel();
|
||||
t1.join();
|
||||
}
|
||||
|
||||
}}} // namespace Azure::Core::Test
|
||||
|
||||
@ -31,7 +31,7 @@ stages:
|
||||
- template: ../../eng/pipelines/templates/stages/archetype-sdk-client.yml
|
||||
parameters:
|
||||
ServiceDirectory: core
|
||||
CtestRegex: azure-core
|
||||
CtestRegex: azure-core.
|
||||
Artifacts:
|
||||
- Name: azure-core
|
||||
Path: azure-core
|
||||
|
||||
Loading…
Reference in New Issue
Block a user