Skip to content

Commit 63dfa8a

Browse files
Makes mocks and errors consistent. Closes #453 (#457)
1 parent 0c7e1cf commit 63dfa8a

17 files changed

+315
-242
lines changed

dev-proxy-plugins/Behavior/RateLimitingPlugin.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ _urlsToWatch is null ||
229229
_resourcesRemaining = 0;
230230
var request = e.Session.HttpClient.Request;
231231

232-
_logger?.LogRequest(new[] { $"Exceeded resource limit when calling {request.Url}.", "Request will be throttled" }, MessageType.Failed, new LoggingContext(e.Session));
232+
_logger?.LogRequest([$"Exceeded resource limit when calling {request.Url}.", "Request will be throttled"], MessageType.Failed, new LoggingContext(e.Session));
233233
if (_configuration.WhenLimitExceeded == RateLimitResponseWhenLimitExceeded.Throttle)
234234
{
235235
e.ThrottledRequests.Add(new ThrottlerInfo(
@@ -244,12 +244,12 @@ _urlsToWatch is null ||
244244
{
245245
if (_configuration.CustomResponse is not null)
246246
{
247-
var headers = _configuration.CustomResponse.ResponseHeaders is not null ?
248-
_configuration.CustomResponse.ResponseHeaders.Select(h => new HttpHeader(h.Key, h.Value)) :
247+
var headers = _configuration.CustomResponse.Response?.Headers is not null ?
248+
_configuration.CustomResponse.Response.Headers.Select(h => new HttpHeader(h.Key, h.Value)) :
249249
Array.Empty<HttpHeader>();
250250

251251
// allow custom throttling response
252-
var responseCode = (HttpStatusCode)(_configuration.CustomResponse.ResponseCode ?? 200);
252+
var responseCode = (HttpStatusCode)(_configuration.CustomResponse.Response?.StatusCode ?? 200);
253253
if (responseCode == HttpStatusCode.TooManyRequests)
254254
{
255255
e.ThrottledRequests.Add(new ThrottlerInfo(
@@ -259,15 +259,15 @@ _urlsToWatch is null ||
259259
));
260260
}
261261

262-
string body = _configuration.CustomResponse.ResponseBody is not null ?
263-
JsonSerializer.Serialize(_configuration.CustomResponse.ResponseBody, new JsonSerializerOptions { WriteIndented = true }) :
262+
string body = _configuration.CustomResponse.Response?.Body is not null ?
263+
JsonSerializer.Serialize(_configuration.CustomResponse.Response.Body, new JsonSerializerOptions { WriteIndented = true }) :
264264
"";
265265
e.Session.GenericResponse(body, responseCode, headers);
266266
state.HasBeenSet = true;
267267
}
268268
else
269269
{
270-
_logger?.LogRequest(new[] { $"Custom behavior not set. {_configuration.CustomResponseFile} not found." }, MessageType.Failed, new LoggingContext(e.Session));
270+
_logger?.LogRequest([$"Custom behavior not set. {_configuration.CustomResponseFile} not found."], MessageType.Failed, new LoggingContext(e.Session));
271271
}
272272
}
273273
}

dev-proxy-plugins/GenericErrorResponse.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,4 @@ public class GenericErrorResponse
1313
public Dictionary<string, string>? Headers { get; set; }
1414
[JsonPropertyName("body")]
1515
public dynamic? Body { get; set; }
16-
[JsonPropertyName("addDynamicRetryAfter")]
17-
public bool? AddDynamicRetryAfter { get; set; } = false;
1816
}

dev-proxy-plugins/MockResponses/GraphMockResponsePlugin.cs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,22 @@ protected override async Task OnRequest(object? sender, ProxyRequestArgs e)
6262
}
6363
};
6464

65-
_logger?.LogRequest(new[] { $"502 {request.Url}" }, MessageType.Mocked, new LoggingContext(e.Session));
65+
_logger?.LogRequest([$"502 {request.Url}"], MessageType.Mocked, new LoggingContext(e.Session));
6666
}
6767
else
6868
{
6969
dynamic? body = null;
7070
var statusCode = HttpStatusCode.OK;
71-
if (mockResponse.ResponseCode is not null)
71+
if (mockResponse.Response?.StatusCode is not null)
7272
{
73-
statusCode = (HttpStatusCode)mockResponse.ResponseCode;
73+
statusCode = (HttpStatusCode)mockResponse.Response.StatusCode;
7474
}
7575

76-
if (mockResponse.ResponseHeaders is not null)
76+
if (mockResponse.Response?.Headers is not null)
7777
{
78-
foreach (var key in mockResponse.ResponseHeaders.Keys)
78+
foreach (var key in mockResponse.Response.Headers.Keys)
7979
{
80-
headers[key] = mockResponse.ResponseHeaders[key];
80+
headers[key] = mockResponse.Response.Headers[key];
8181
}
8282
}
8383
// default the content type to application/json unless set in the mock response
@@ -86,9 +86,9 @@ protected override async Task OnRequest(object? sender, ProxyRequestArgs e)
8686
headers.Add("content-type", "application/json");
8787
}
8888

89-
if (mockResponse.ResponseBody is not null)
89+
if (mockResponse.Response?.Body is not null)
9090
{
91-
var bodyString = JsonSerializer.Serialize(mockResponse.ResponseBody) as string;
91+
var bodyString = JsonSerializer.Serialize(mockResponse.Response.Body) as string;
9292
// we get a JSON string so need to start with the opening quote
9393
if (bodyString?.StartsWith("\"@") ?? false)
9494
{
@@ -111,7 +111,7 @@ protected override async Task OnRequest(object? sender, ProxyRequestArgs e)
111111
}
112112
else
113113
{
114-
body = mockResponse.ResponseBody;
114+
body = mockResponse.Response.Body;
115115
}
116116
}
117117
response = new GraphBatchResponsePayloadResponse
@@ -122,7 +122,7 @@ protected override async Task OnRequest(object? sender, ProxyRequestArgs e)
122122
Body = body
123123
};
124124

125-
_logger?.LogRequest(new[] { $"{mockResponse.ResponseCode ?? 200} {mockResponse.Url}" }, MessageType.Mocked, new LoggingContext(e.Session));
125+
_logger?.LogRequest([$"{mockResponse.Response?.StatusCode ?? 200} {mockResponse.Request?.Url}"], MessageType.Mocked, new LoggingContext(e.Session));
126126
}
127127

128128
responses.Add(response);
@@ -143,34 +143,34 @@ protected override async Task OnRequest(object? sender, ProxyRequestArgs e)
143143
protected MockResponse? GetMatchingMockResponse(GraphBatchRequestPayloadRequest request, Uri batchRequestUri)
144144
{
145145
if (_configuration.NoMocks ||
146-
_configuration.Responses is null ||
147-
!_configuration.Responses.Any())
146+
_configuration.Mocks is null ||
147+
!_configuration.Mocks.Any())
148148
{
149149
return null;
150150
}
151151

152-
var mockResponse = _configuration.Responses.FirstOrDefault(mockResponse =>
152+
var mockResponse = _configuration.Mocks.FirstOrDefault(mockResponse =>
153153
{
154-
if (mockResponse.Method != request.Method) return false;
154+
if (mockResponse.Request?.Method != request.Method) return false;
155155
// URLs in batch are relative to Graph version number so we need
156156
// to make them absolute using the batch request URL
157157
var absoluteRequestFromBatchUrl = ProxyUtils
158158
.GetAbsoluteRequestUrlFromBatch(batchRequestUri, request.Url)
159159
.ToString();
160-
if (mockResponse.Url == absoluteRequestFromBatchUrl)
160+
if (mockResponse.Request.Url == absoluteRequestFromBatchUrl)
161161
{
162162
return true;
163163
}
164164

165165
// check if the URL contains a wildcard
166166
// if it doesn't, it's not a match for the current request for sure
167-
if (!mockResponse.Url.Contains('*'))
167+
if (!mockResponse.Request.Url.Contains('*'))
168168
{
169169
return false;
170170
}
171171

172172
//turn mock URL with wildcard into a regex and match against the request URL
173-
var mockResponseUrlRegex = Regex.Escape(mockResponse.Url).Replace("\\*", ".*");
173+
var mockResponseUrlRegex = Regex.Escape(mockResponse.Request.Url).Replace("\\*", ".*");
174174
return Regex.IsMatch(absoluteRequestFromBatchUrl, $"^{mockResponseUrlRegex}$");
175175
});
176176
return mockResponse;

dev-proxy-plugins/MockResponses/MockResponse.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,29 @@
66
namespace Microsoft.DevProxy.Plugins.MockResponses;
77

88
public class MockResponse
9+
{
10+
[JsonPropertyName("request")]
11+
public MockResponseRequest? Request { get; set; }
12+
[JsonPropertyName("response")]
13+
public MockResponseResponse? Response { get; set; }
14+
}
15+
16+
public class MockResponseRequest
917
{
1018
[JsonPropertyName("url")]
1119
public string Url { get; set; } = string.Empty;
1220
[JsonPropertyName("method")]
1321
public string Method { get; set; } = "GET";
1422
[JsonPropertyName("nth"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
1523
public int? Nth { get; set; }
16-
[JsonPropertyName("responseCode")]
17-
public int? ResponseCode { get; set; } = 200;
18-
[JsonPropertyName("responseBody")]
19-
public dynamic? ResponseBody { get; set; }
20-
[JsonPropertyName("responseHeaders")]
21-
public IDictionary<string, string>? ResponseHeaders { get; set; }
2224
}
25+
26+
public class MockResponseResponse
27+
{
28+
[JsonPropertyName("statusCode")]
29+
public int? StatusCode { get; set; } = 200;
30+
[JsonPropertyName("body")]
31+
public dynamic? Body { get; set; }
32+
[JsonPropertyName("headers")]
33+
public IDictionary<string, string>? Headers { get; set; }
34+
}

dev-proxy-plugins/MockResponses/MockResponsePlugin.cs

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ public class MockResponseConfiguration
2020
[JsonIgnore]
2121
public bool NoMocks { get; set; } = false;
2222
[JsonIgnore]
23-
public string MocksFile { get; set; } = "responses.json";
23+
public string MocksFile { get; set; } = "mocks.json";
2424
[JsonIgnore]
2525
public bool BlockUnmockedRequests { get; set; } = false;
2626

2727
[JsonPropertyName("$schema")]
28-
public string Schema { get; set; } = "https://raw.githubusercontent.com/microsoft/dev-proxy/main/schemas/v1.0/responses.schema.json";
29-
[JsonPropertyName("responses")]
30-
public IEnumerable<MockResponse> Responses { get; set; } = Array.Empty<MockResponse>();
28+
public string Schema { get; set; } = "https://raw.githubusercontent.com/microsoft/dev-proxy/main/schemas/v1.0/mockresponseplugin.schema.json";
29+
[JsonPropertyName("mocks")]
30+
public IEnumerable<MockResponse> Mocks { get; set; } = Array.Empty<MockResponse>();
3131
}
3232

3333
public class MockResponsePlugin : BaseProxyPlugin
@@ -48,8 +48,10 @@ public MockResponsePlugin()
4848
_noMocks.AddAlias("-n");
4949
_noMocks.ArgumentHelpName = "no mocks";
5050

51-
_mocksFile = new Option<string?>("--mocks-file", "Provide a file populated with mock responses");
52-
_mocksFile.ArgumentHelpName = "mocks file";
51+
_mocksFile = new Option<string?>("--mocks-file", "Provide a file populated with mock responses")
52+
{
53+
ArgumentHelpName = "mocks file"
54+
};
5355
}
5456

5557
public override void Register(IPluginEvents pluginEvents,
@@ -120,14 +122,20 @@ protected virtual Task OnRequest(object? sender, ProxyRequestArgs e)
120122
{
121123
ProcessMockResponse(e.Session, new MockResponse
122124
{
123-
Method = request.Method,
124-
Url = request.Url,
125-
ResponseCode = 502,
126-
ResponseBody = new GraphErrorResponseBody(new GraphErrorResponseError
125+
Request = new()
126+
{
127+
Url = request.Url,
128+
Method = request.Method
129+
},
130+
Response = new()
127131
{
128-
Code = "Bad Gateway",
129-
Message = $"No mock response found for {request.Method} {request.Url}"
130-
})
132+
StatusCode = 502,
133+
Body = new GraphErrorResponseBody(new GraphErrorResponseError
134+
{
135+
Code = "Bad Gateway",
136+
Message = $"No mock response found for {request.Method} {request.Url}"
137+
})
138+
}
131139
});
132140
state.HasBeenSet = true;
133141
}
@@ -139,57 +147,59 @@ protected virtual Task OnRequest(object? sender, ProxyRequestArgs e)
139147
private MockResponse? GetMatchingMockResponse(Request request)
140148
{
141149
if (_configuration.NoMocks ||
142-
_configuration.Responses is null ||
143-
!_configuration.Responses.Any())
150+
_configuration.Mocks is null ||
151+
!_configuration.Mocks.Any())
144152
{
145153
return null;
146154
}
147155

148-
var mockResponse = _configuration.Responses.FirstOrDefault(mockResponse =>
156+
var mockResponse = _configuration.Mocks.FirstOrDefault(mockResponse =>
149157
{
150-
if (mockResponse.Method != request.Method) return false;
151-
if (mockResponse.Url == request.Url && IsNthRequest(mockResponse))
158+
if (mockResponse.Request is null) return false;
159+
160+
if (mockResponse.Request.Method != request.Method) return false;
161+
if (mockResponse.Request.Url == request.Url && IsNthRequest(mockResponse))
152162
{
153163
return true;
154164
}
155165

156166
// check if the URL contains a wildcard
157167
// if it doesn't, it's not a match for the current request for sure
158-
if (!mockResponse.Url.Contains('*'))
168+
if (!mockResponse.Request.Url.Contains('*'))
159169
{
160170
return false;
161171
}
162172

163173
//turn mock URL with wildcard into a regex and match against the request URL
164-
var mockResponseUrlRegex = Regex.Escape(mockResponse.Url).Replace("\\*", ".*");
174+
var mockResponseUrlRegex = Regex.Escape(mockResponse.Request.Url).Replace("\\*", ".*");
165175
return Regex.IsMatch(request.Url, $"^{mockResponseUrlRegex}$") && IsNthRequest(mockResponse);
166176
});
167177

168-
if (mockResponse is not null)
178+
if (mockResponse is not null && mockResponse.Request is not null)
169179
{
170-
if (!_appliedMocks.ContainsKey(mockResponse.Url))
180+
if (!_appliedMocks.ContainsKey(mockResponse.Request.Url))
171181
{
172-
_appliedMocks.Add(mockResponse.Url, 0);
182+
_appliedMocks.Add(mockResponse.Request.Url, 0);
173183
}
174-
_appliedMocks[mockResponse.Url]++;
184+
_appliedMocks[mockResponse.Request.Url]++;
175185
}
176186

177187
return mockResponse;
178188
}
179189

180190
private bool IsNthRequest(MockResponse mockResponse)
181191
{
182-
if (mockResponse.Nth is null)
192+
if (mockResponse.Request is null || mockResponse.Request.Nth is null)
183193
{
184194
// mock doesn't define an Nth property so it always qualifies
185195
return true;
186196
}
187197

188-
var nth = 0;
189-
_appliedMocks.TryGetValue(mockResponse.Url, out nth);
198+
int nth;
199+
_appliedMocks.TryGetValue(mockResponse.Request.Url, out nth);
190200
nth++;
191201

192-
return mockResponse.Nth == nth;
202+
return mockResponse.Request.Nth == nth;
193203
}
194204

195205
private void ProcessMockResponse(SessionEventArgs e, MockResponse matchingResponse)
@@ -199,14 +209,14 @@ private void ProcessMockResponse(SessionEventArgs e, MockResponse matchingRespon
199209
string requestDate = DateTime.Now.ToString();
200210
var headers = ProxyUtils.BuildGraphResponseHeaders(e.HttpClient.Request, requestId, requestDate);
201211
HttpStatusCode statusCode = HttpStatusCode.OK;
202-
if (matchingResponse.ResponseCode is not null)
212+
if (matchingResponse.Response?.StatusCode is not null)
203213
{
204-
statusCode = (HttpStatusCode)matchingResponse.ResponseCode;
214+
statusCode = (HttpStatusCode)matchingResponse.Response.StatusCode;
205215
}
206216

207-
if (matchingResponse.ResponseHeaders is not null)
217+
if (matchingResponse.Response?.Headers is not null)
208218
{
209-
foreach (var key in matchingResponse.ResponseHeaders.Keys)
219+
foreach (var key in matchingResponse.Response.Headers.Keys)
210220
{
211221
// remove duplicate headers
212222
var existingHeader = headers.FirstOrDefault(h => h.Name.Equals(key, StringComparison.OrdinalIgnoreCase));
@@ -215,7 +225,7 @@ private void ProcessMockResponse(SessionEventArgs e, MockResponse matchingRespon
215225
headers.Remove(existingHeader);
216226
}
217227

218-
headers.Add(new HttpHeader(key, matchingResponse.ResponseHeaders[key]));
228+
headers.Add(new HttpHeader(key, matchingResponse.Response.Headers[key]));
219229
}
220230
}
221231
// default the content type to application/json unless set in the mock response
@@ -224,9 +234,9 @@ private void ProcessMockResponse(SessionEventArgs e, MockResponse matchingRespon
224234
headers.Add(new HttpHeader("content-type", "application/json"));
225235
}
226236

227-
if (matchingResponse.ResponseBody is not null)
237+
if (matchingResponse.Response?.Body is not null)
228238
{
229-
var bodyString = JsonSerializer.Serialize(matchingResponse.ResponseBody) as string;
239+
var bodyString = JsonSerializer.Serialize(matchingResponse.Response.Body) as string;
230240
// we get a JSON string so need to start with the opening quote
231241
if (bodyString?.StartsWith("\"@") ?? false)
232242
{
@@ -245,7 +255,7 @@ private void ProcessMockResponse(SessionEventArgs e, MockResponse matchingRespon
245255
{
246256
var bodyBytes = File.ReadAllBytes(filePath);
247257
e.GenericResponse(bodyBytes, statusCode, headers);
248-
_logger?.LogRequest(new[] { $"{matchingResponse.ResponseCode ?? 200} {matchingResponse.Url}" }, MessageType.Mocked, new LoggingContext(e));
258+
_logger?.LogRequest([$"{matchingResponse.Response.StatusCode ?? 200} {matchingResponse.Request?.Url}"], MessageType.Mocked, new LoggingContext(e));
249259
return;
250260
}
251261
}
@@ -256,6 +266,6 @@ private void ProcessMockResponse(SessionEventArgs e, MockResponse matchingRespon
256266
}
257267
e.GenericResponse(body ?? string.Empty, statusCode, headers);
258268

259-
_logger?.LogRequest(new[] { $"{matchingResponse.ResponseCode ?? 200} {matchingResponse.Url}" }, MessageType.Mocked, new LoggingContext(e));
269+
_logger?.LogRequest([$"{matchingResponse.Response?.StatusCode ?? 200} {matchingResponse.Request?.Url}"], MessageType.Mocked, new LoggingContext(e));
260270
}
261271
}

0 commit comments

Comments
 (0)