Skip to content

Commit 9b0cd3a

Browse files
authored
Mock updates (microsoft#447)
* Add support for getting an intermediate callback when a mock is matched. * Add ability to remove a single mock without having to clear them all.
1 parent 16560f6 commit 9b0cd3a

File tree

8 files changed

+144
-27
lines changed

8 files changed

+144
-27
lines changed

Include/httpClient/mock.h

+39
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,45 @@ STDAPI HCMockAddMock(
7272
_In_ uint32_t requestBodySize
7373
) noexcept;
7474

75+
/// <summary>
76+
/// Callback to be invoked when a mock is matched (before completing the call).
77+
/// </summary>
78+
/// <param name="matchedMock">The matched mock.</param>
79+
/// <param name="method">Method of the original call.</param>
80+
/// <param name="url">Url of the original call.</param>
81+
/// <param name="requestBodyBytes">Request body from the original call.</param>
82+
/// <param name="requestBodySize">Size of the request body.</param>
83+
/// <param name="context">Client context.</param>
84+
typedef void (CALLBACK* HCMockMatchedCallback)(
85+
_In_ HCMockCallHandle matchedMock,
86+
_In_ const char* method,
87+
_In_ const char* url,
88+
_In_ const uint8_t* requestBodyBytes,
89+
_In_ uint32_t requestBodySize,
90+
_In_ void* context
91+
);
92+
93+
/// <summary>
94+
/// Add an intermediate callback that will be called when the specified mock is matched.
95+
/// This gives the client another opportunity to specify the mock response.
96+
/// </summary>
97+
/// <param name="call">The matched mock.</param>
98+
/// <param name="callback">Callback to be invoked when the mock is matched.</param>
99+
/// <returns>Result code for this API operation. Possible values are S_OK, E_INVALIDARG, or E_FAIL.</returns>
100+
STDAPI HCMockSetMockMatchedCallback(
101+
_In_ HCMockCallHandle call,
102+
_In_ HCMockMatchedCallback callback,
103+
_In_opt_ void* context
104+
);
105+
106+
/// <summary>
107+
/// Removes and cleans up the mock.
108+
/// </summary>
109+
/// <returns>Result code for this API operation. Possible values are S_OK, or E_FAIL.</returns>
110+
STDAPI HCMockRemoveMock(
111+
_In_ HCMockCallHandle call
112+
);
113+
75114
/// <summary>
76115
/// Removes and cleans up all mock calls added by HCMockAddMock
77116
/// </summary>

Include/httpClient/pal.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ typedef struct _LIST_ENTRY {
440440
typedef uint32_t HCMemoryType;
441441
typedef struct HC_WEBSOCKET* HCWebsocketHandle;
442442
typedef struct HC_CALL* HCCallHandle;
443-
typedef struct HC_CALL* HCMockCallHandle;
443+
typedef struct HC_MOCK_CALL* HCMockCallHandle;
444444
typedef struct HC_PERFORM_ENV* HCPerformEnv;
445445

446446
extern "C"

Source/Global/global.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../HTTP/httpcall.h"
66
#include "buildver.h"
77
#include "global.h"
8+
#include "../Mock/lhc_mock.h"
89

910
#if !HC_NOWEBSOCKETS
1011
#include "../WebSocket/hcwebsocket.h"

Source/Global/global.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,8 @@ typedef struct http_singleton
7878

7979
// Mock state
8080
std::recursive_mutex m_mocksLock;
81-
http_internal_vector<HC_CALL*> m_mocks;
82-
HC_CALL* m_lastMatchingMock = nullptr;
83-
bool m_mocksEnabled = false;
81+
http_internal_vector<HC_MOCK_CALL*> m_mocks;
82+
HC_MOCK_CALL* m_lastMatchingMock = nullptr;
8483

8584
std::recursive_mutex m_sharedPtrsLock;
8685
http_internal_unordered_map<void*, std::shared_ptr<void>> m_sharedPtrs;

Source/HTTP/httpcall.cpp

+5-8
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,13 @@ HRESULT perform_http_call(
114114
{
115115
HCCallHandle call = static_cast<HCCallHandle>(data->context);
116116
bool matchedMocks = false;
117-
if (httpSingleton->m_mocksEnabled)
117+
118+
matchedMocks = Mock_Internal_HCHttpCallPerformAsync(call);
119+
if (matchedMocks)
118120
{
119-
matchedMocks = Mock_Internal_HCHttpCallPerformAsync(call);
120-
if (matchedMocks)
121-
{
122-
XAsyncComplete(data->async, S_OK, 0);
123-
}
121+
XAsyncComplete(data->async, S_OK, 0);
124122
}
125-
126-
if (!matchedMocks) // if there wasn't a matched mock, then real call
123+
else // if there wasn't a matched mock, then real call
127124
{
128125
HttpPerformInfo const& info = httpSingleton->m_httpPerform;
129126
if (info.handler != nullptr)

Source/Mock/lhc_mock.cpp

+22-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ bool DoesMockCallMatch(_In_ const HC_CALL* mockCall, _In_ const HC_CALL* origina
1515
}
1616
else
1717
{
18-
if (originalCall->url == mockCall->url)
18+
if (originalCall->url.substr(0, mockCall->url.size()) == mockCall->url)
1919
{
2020
if (mockCall->requestBodyBytes.empty())
2121
{
@@ -34,7 +34,7 @@ bool DoesMockCallMatch(_In_ const HC_CALL* mockCall, _In_ const HC_CALL* origina
3434
return false;
3535
}
3636

37-
long GetIndexOfMock(const http_internal_vector<HC_CALL*>& mocks, HC_CALL* lastMatchingMock)
37+
long GetIndexOfMock(const http_internal_vector<HC_MOCK_CALL*>& mocks, HC_MOCK_CALL* lastMatchingMock)
3838
{
3939
if (lastMatchingMock == nullptr)
4040
{
@@ -52,17 +52,19 @@ long GetIndexOfMock(const http_internal_vector<HC_CALL*>& mocks, HC_CALL* lastMa
5252
return -1;
5353
}
5454

55-
HC_CALL* GetMatchingMock(
55+
HC_MOCK_CALL* GetMatchingMock(
5656
_In_ HC_CALL* originalCall
57-
)
57+
)
5858
{
5959
auto httpSingleton = get_http_singleton(false);
6060
if (nullptr == httpSingleton)
61+
{
6162
return nullptr;
63+
}
6264

63-
http_internal_vector<HC_CALL*> mocks;
64-
HC_CALL* lastMatchingMock = nullptr;
65-
HC_CALL* matchingMock = nullptr;
65+
http_internal_vector<HC_MOCK_CALL*> mocks;
66+
HC_MOCK_CALL* lastMatchingMock = nullptr;
67+
HC_MOCK_CALL* matchingMock = nullptr;
6668

6769
{
6870
std::lock_guard<std::recursive_mutex> guard(httpSingleton->m_mocksLock);
@@ -121,12 +123,24 @@ bool Mock_Internal_HCHttpCallPerformAsync(
121123
)
122124
{
123125
HC_CALL* originalCall = static_cast<HC_CALL*>(originalCallHandle);
124-
HC_CALL* matchingMock = GetMatchingMock(originalCall);
126+
HC_MOCK_CALL* matchingMock = GetMatchingMock(originalCall);
125127
if (matchingMock == nullptr)
126128
{
127129
return false;
128130
}
129131

132+
if (matchingMock->matchedCallback)
133+
{
134+
matchingMock->matchedCallback(
135+
matchingMock,
136+
originalCall->method.data(),
137+
originalCall->url.data(),
138+
originalCall->requestBodyBytes.data(),
139+
static_cast<uint32_t>(originalCall->requestBodyBytes.size()),
140+
matchingMock->matchCallbackContext
141+
);
142+
}
143+
130144
size_t byteBuf;
131145
HCHttpCallResponseGetResponseBodyBytesSize(matchingMock, &byteBuf);
132146
http_memory_buffer buffer(byteBuf);

Source/Mock/lhc_mock.h

+6
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,10 @@
44
#pragma once
55
#include "pch.h"
66

7+
struct HC_MOCK_CALL : public HC_CALL
8+
{
9+
HCMockMatchedCallback matchedCallback{ nullptr };
10+
void* matchCallbackContext{ nullptr };
11+
};
12+
713
bool Mock_Internal_HCHttpCallPerformAsync(_In_ HCCallHandle originalCall);

Source/Mock/mock_publics.cpp

+68-7
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,33 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
#include "pch.h"
5+
#include "lhc_mock.h"
56

67
using namespace xbox::httpclient;
78
using namespace xbox::httpclient::log;
89

9-
10-
STDAPI
11-
HCMockCallCreate(
12-
_Out_ HCMockCallHandle* call
10+
STDAPI HCMockCallCreate(
11+
_Out_ HCMockCallHandle* callHandle
1312
) noexcept
1413
{
15-
return HCHttpCallCreate(call);
14+
if (callHandle == nullptr)
15+
{
16+
return E_INVALIDARG;
17+
}
18+
19+
auto httpSingleton = get_http_singleton(true);
20+
if (nullptr == httpSingleton)
21+
{
22+
return E_HC_NOT_INITIALISED;
23+
}
24+
25+
HC_MOCK_CALL* call = new HC_MOCK_CALL{};
26+
call->id = ++httpSingleton->m_lastId;
27+
28+
HC_TRACE_INFORMATION(HTTPCLIENT, "HCMockCallCreate [ID %llu]", call->id);
29+
30+
*callHandle = call;
31+
return S_OK;
1632
}
1733

1834
STDAPI
@@ -32,7 +48,9 @@ try
3248

3349
auto httpSingleton = get_http_singleton(true);
3450
if (nullptr == httpSingleton)
51+
{
3552
return E_HC_NOT_INITIALISED;
53+
}
3654

3755
if (method != nullptr && url != nullptr)
3856
{
@@ -54,11 +72,55 @@ try
5472

5573
std::lock_guard<std::recursive_mutex> guard(httpSingleton->m_mocksLock);
5674
httpSingleton->m_mocks.push_back(call);
57-
httpSingleton->m_mocksEnabled = true;
5875
return S_OK;
5976
}
6077
CATCH_RETURN()
6178

79+
STDAPI HCMockSetMockMatchedCallback(
80+
_In_ HCMockCallHandle call,
81+
_In_ HCMockMatchedCallback callback,
82+
_In_opt_ void* context
83+
)
84+
try
85+
{
86+
if (call == nullptr)
87+
{
88+
return E_INVALIDARG;
89+
}
90+
91+
call->matchedCallback = callback;
92+
call->matchCallbackContext = context;
93+
return S_OK;
94+
}
95+
CATCH_RETURN()
96+
97+
STDAPI HCMockRemoveMock(
98+
_In_ HCMockCallHandle call
99+
)
100+
try
101+
{
102+
auto httpSingleton = get_http_singleton(false);
103+
if (nullptr == httpSingleton)
104+
{
105+
return E_HC_NOT_INITIALISED;
106+
}
107+
108+
std::lock_guard<std::recursive_mutex> guard(httpSingleton->m_mocksLock);
109+
auto& mocks{ httpSingleton->m_mocks };
110+
111+
for (auto iter = mocks.begin(); iter != mocks.end(); ++iter)
112+
{
113+
if (*iter == call)
114+
{
115+
mocks.erase(iter);
116+
return S_OK;
117+
}
118+
}
119+
120+
return E_INVALIDARG; // Mock not found
121+
}
122+
CATCH_RETURN()
123+
62124
STDAPI
63125
HCMockClearMocks() noexcept
64126
try
@@ -75,7 +137,6 @@ try
75137
}
76138

77139
httpSingleton->m_mocks.clear();
78-
httpSingleton->m_mocksEnabled = false;
79140
return S_OK;
80141
}
81142
CATCH_RETURN()

0 commit comments

Comments
 (0)