Skip to content

Commit e06d74c

Browse files
committed
Support for global and endpoint defaults
1 parent e8a6439 commit e06d74c

File tree

5 files changed

+241
-13
lines changed

5 files changed

+241
-13
lines changed

UnitTests/TestDefaults.cs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using Newtonsoft.Json;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using netmockery;
7+
using Xunit;
8+
9+
namespace UnitTests
10+
{
11+
public class TestDefaults : IDisposable
12+
{
13+
DirectoryCreator directoryCreator = new DirectoryCreator();
14+
EndpointCollection endpointCollection;
15+
16+
public void InitializeEndpointCollectionWithoutDefaults()
17+
{
18+
var jsonEndpoint1 = new JSONEndpoint
19+
{
20+
name = "foobar",
21+
pathregex = "foobar",
22+
responses = new[] {
23+
new JSONResponse {
24+
match = new JSONRequestMatcher(),
25+
file = "myfile.xml"
26+
}
27+
}
28+
};
29+
30+
directoryCreator.AddFile("endpoint1\\endpoint.json", JsonConvert.SerializeObject(jsonEndpoint1));
31+
32+
endpointCollection = EndpointCollectionReader.ReadFromDirectory(directoryCreator.DirectoryName);
33+
}
34+
35+
public void InitializeEndpointCollectionWithGlobalDefaultsOnly()
36+
{
37+
var jsonEndpoint1 = new JSONEndpoint
38+
{
39+
name = "foobar",
40+
pathregex = "foobar",
41+
responses = new[] {
42+
new JSONResponse {
43+
match = new JSONRequestMatcher(),
44+
file = "myfile.xml"
45+
}
46+
}
47+
};
48+
49+
var jsonEndpoint2 = new JSONEndpoint
50+
{
51+
name = "baz",
52+
pathregex = "baz",
53+
responses = new[] {
54+
new JSONResponse {
55+
match = new JSONRequestMatcher(),
56+
file = "myfile.xml",
57+
contenttype = "text/xml",
58+
charset = "utf-8"
59+
}
60+
}
61+
};
62+
63+
directoryCreator.AddFile("defaults.json", JsonConvert.SerializeObject(new JSONDefaults { charset = "ascii", contenttype = "application/xml" }));
64+
directoryCreator.AddFile("endpoint1\\endpoint.json", JsonConvert.SerializeObject(jsonEndpoint1));
65+
directoryCreator.AddFile("endpoint2\\endpoint.json", JsonConvert.SerializeObject(jsonEndpoint2));
66+
67+
endpointCollection = EndpointCollectionReader.ReadFromDirectory(directoryCreator.DirectoryName);
68+
}
69+
70+
public void InitializeEndpointCollectionWithGlobalAndEndpointDefaults()
71+
{
72+
var jsonEndpoint1 = new JSONEndpoint
73+
{
74+
name = "noendpointdefaults",
75+
pathregex = "noendpointdefaults",
76+
responses = new[] {
77+
new JSONResponse {
78+
match = new JSONRequestMatcher(),
79+
file = "myfile.xml"
80+
}
81+
}
82+
};
83+
84+
var jsonEndpoint2 = new JSONEndpoint
85+
{
86+
name = "endpointdefaults",
87+
pathregex = "endpointdefaults",
88+
responses = new[] {
89+
new JSONResponse {
90+
match = new JSONRequestMatcher(),
91+
file = "myfile.xml",
92+
}
93+
}
94+
};
95+
var globalDefaults = new JSONDefaults { charset = "ascii", contenttype = "application/xml" };
96+
var endpointDefaults = new JSONDefaults { charset = "UTF-7", contenttype = "text/plain" };
97+
98+
directoryCreator.AddFile("defaults.json", JsonConvert.SerializeObject(globalDefaults));
99+
directoryCreator.AddFile("endpoint1\\endpoint.json", JsonConvert.SerializeObject(jsonEndpoint1));
100+
directoryCreator.AddFile("endpoint2\\endpoint.json", JsonConvert.SerializeObject(jsonEndpoint2));
101+
directoryCreator.AddFile("endpoint2\\defaults.json", JsonConvert.SerializeObject(endpointDefaults));
102+
103+
endpointCollection = EndpointCollectionReader.ReadFromDirectory(directoryCreator.DirectoryName);
104+
105+
}
106+
107+
public void Dispose()
108+
{
109+
directoryCreator.Dispose();
110+
}
111+
112+
[Fact]
113+
public void EndpointDefaultsAreApplied()
114+
{
115+
InitializeEndpointCollectionWithGlobalAndEndpointDefaults();
116+
117+
var responseCreator = endpointCollection.Get("endpointdefaults").Responses.Single().Item2 as SimpleResponseCreator;
118+
Assert.Equal("text/plain", responseCreator.ContentType);
119+
Assert.Equal("utf-7", responseCreator.Encoding.WebName);
120+
}
121+
122+
[Fact]
123+
public void GlobalDefaultsAreApplied()
124+
{
125+
InitializeEndpointCollectionWithGlobalDefaultsOnly();
126+
127+
var endpoint = endpointCollection.Get("foobar");
128+
var responseCreator = endpoint.Responses.Single().Item2 as SimpleResponseCreator;
129+
Assert.Equal("application/xml", responseCreator.ContentType);
130+
Assert.Equal("us-ascii", responseCreator.Encoding.WebName);
131+
}
132+
133+
[Fact]
134+
public void DefaultsCanBeOverridden()
135+
{
136+
InitializeEndpointCollectionWithGlobalDefaultsOnly();
137+
138+
var endpoint = endpointCollection.Get("baz");
139+
var responseCreator = endpoint.Responses.Single().Item2 as SimpleResponseCreator;
140+
Assert.Equal("text/xml", responseCreator.ContentType);
141+
Assert.Equal("utf-8", responseCreator.Encoding.WebName);
142+
}
143+
144+
[Fact]
145+
public void ValidateDefaultEncodingWithoutDefaults()
146+
{
147+
InitializeEndpointCollectionWithoutDefaults();
148+
149+
var responseCreator = endpointCollection.Get("foobar").Responses.Single().Item2 as SimpleResponseCreator;
150+
Assert.Equal("utf-8", responseCreator.Encoding.WebName);
151+
}
152+
}
153+
}

UnitTests/TestInitFromJSON.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ public class TestInitFromJSON
3737
[Fact]
3838
public void SimpleEndpointAttributes()
3939
{
40-
var endpoint = JSONReader.ReadEndpoint(ENDPOINTJSON, "p:\\ath\\to\\endpoint\\directory");
40+
var endpoint = JSONReader.ReadEndpoint(ENDPOINTJSON, "p:\\ath\\to\\endpoint\\directory", globalDefaults: null);
4141
Assert.Equal("foo", endpoint.Name);
4242
Assert.Equal("^/foo/$", endpoint.PathRegex);
4343
}
4444

4545
private Tuple<RequestMatcher, ResponseCreator> ParseResponse(string json)
4646
{
47-
var endpoint = JSONReader.ReadEndpoint("{'name': 'foo', 'pathregex': 'foo', 'responses': [" + json + "]}", "r:\\oot\\directory");
47+
var endpoint = JSONReader.ReadEndpoint("{'name': 'foo', 'pathregex': 'foo', 'responses': [" + json + "]}", "r:\\oot\\directory", globalDefaults: null);
4848
var responses = endpoint.Responses.ToArray();
4949
Debug.Assert(responses.Length == 1);
5050
return responses[0];
@@ -53,7 +53,7 @@ private Tuple<RequestMatcher, ResponseCreator> ParseResponse(string json)
5353
[Fact]
5454
public void Responses()
5555
{
56-
var endpoint = JSONReader.ReadEndpoint(ENDPOINTJSON, "p:\\ath\\to\\endpoint\\directory");
56+
var endpoint = JSONReader.ReadEndpoint(ENDPOINTJSON, "p:\\ath\\to\\endpoint\\directory", globalDefaults: null);
5757
var responses = endpoint.Responses.ToArray();
5858
Assert.Equal(2, responses.Length);
5959

netmockery/EndpointCollectionReader.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Threading.Tasks;
55
using System.IO;
6+
using Newtonsoft.Json;
67

78
namespace netmockery
89
{
@@ -11,12 +12,21 @@ static public class EndpointCollectionReader
1112
static public EndpointCollection ReadFromDirectory(string directoryName)
1213
{
1314
var retval = new EndpointCollection { SourceDirectory = directoryName };
15+
var globalDefaultsFile = Path.Combine(directoryName, "defaults.json");
16+
17+
var globalDefaults =
18+
File.Exists(globalDefaultsFile)
19+
?
20+
JsonConvert.DeserializeObject<JSONDefaults>(File.ReadAllText(globalDefaultsFile))
21+
:
22+
null;
23+
1424
foreach (var subdirectory in Directory.GetDirectories(directoryName))
1525
{
1626
var endpointFile = Path.Combine(subdirectory, "endpoint.json");
1727
if (File.Exists(endpointFile))
1828
{
19-
retval.Add(JSONReader.ReadEndpoint(File.ReadAllText(endpointFile), subdirectory));
29+
retval.Add(JSONReader.ReadEndpoint(File.ReadAllText(endpointFile), subdirectory, globalDefaults));
2030
}
2131
}
2232
return retval;

netmockery/JSONReader.cs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ namespace netmockery
1111
{
1212
public static class JSONReader
1313
{
14-
public static Endpoint ReadEndpoint(string jsonString, string rootDir)
14+
public static Endpoint ReadEndpoint(string jsonString, string rootDir, JSONDefaults globalDefaults)
1515
{
16-
return JsonConvert.DeserializeObject<JSONEndpoint>(jsonString).CreateEndpoint(rootDir);
16+
return JsonConvert.DeserializeObject<JSONEndpoint>(jsonString).CreateEndpoint(rootDir, globalDefaults);
1717
}
1818
}
1919

@@ -105,7 +105,11 @@ public JSONResponse Validated()
105105
}
106106
//TODO: Implement related validation
107107
//TODO: Implement set if main not set validation (i.e. proxy set but not forward)
108+
108109
//TODO: Implement invalid for type validation (i.e. contenttype if a forward response creator)
110+
// but remember this is problematic in the case of global defaults
111+
// unclean solution is to apply defaults after validation
112+
// clean solution is to apply defaults only based on rules, i.e. for forward response creator, do not apply defaults
109113
return this;
110114
}
111115

@@ -218,16 +222,56 @@ public class JSONEndpoint
218222
public JSONResponse[] responses;
219223

220224

221-
public Endpoint CreateEndpoint(string rootDir)
225+
public Endpoint CreateEndpoint(string rootDir, JSONDefaults globalDefaults)
222226
{
223227
var endpoint = new Endpoint(name, pathregex);
228+
229+
var endpointDefaultsFile = Path.Combine(rootDir, "defaults.json");
230+
var endpointDefaults =
231+
File.Exists(endpointDefaultsFile)
232+
?
233+
JsonConvert.DeserializeObject<JSONDefaults>(File.ReadAllText(endpointDefaultsFile))
234+
:
235+
null;
236+
224237
foreach (var jsonResponse in responses)
225238
{
239+
if (endpointDefaults != null)
240+
{
241+
applyDefaults(endpointDefaults, jsonResponse);
242+
}
243+
244+
if (globalDefaults != null)
245+
{
246+
applyDefaults(globalDefaults, jsonResponse);
247+
}
226248
var validatedJsonResponse = jsonResponse.Validated();
227249
endpoint.Add(validatedJsonResponse.match.CreateRequestMatcher(), validatedJsonResponse.CreateResponseCreator(rootDir));
228250
}
229251
endpoint.Directory = rootDir;
230252
return endpoint;
231253
}
254+
255+
private void applyDefaults(JSONDefaults defaults, JSONResponse jsonResponse)
256+
{
257+
Debug.Assert(defaults != null);
258+
Debug.Assert(jsonResponse != null);
259+
260+
if (defaults.charset != null && jsonResponse.charset == null)
261+
{
262+
jsonResponse.charset = defaults.charset;
263+
}
264+
265+
if (defaults.contenttype != null && jsonResponse.contenttype == null)
266+
{
267+
jsonResponse.contenttype = defaults.contenttype;
268+
}
269+
}
270+
}
271+
272+
public class JSONDefaults
273+
{
274+
public string contenttype;
275+
public string charset;
232276
}
233277
}

netmockery/documentation.md

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,27 @@ for more information.
197197
* ``replacements``: TODO: Document. Not used for the forward request response creator.
198198
* ``delay``: If set, netmockery waits for the specified number of seconds before returning the response to the client.
199199

200+
### Defaults
201+
202+
Default ``contenttype`` and ``charset`` can be configured by endpoint and for the entire endpoint collection.
203+
204+
To set defaults for an endpoint, create a ``defaults.json`` file inside the endpoint directory (i.e. in the same directory as ``endpoint.json``).
205+
206+
To set global defaults, create a ``defaults.json`` file in the endpoint collection directory (i.e. in the endpoint collection root directory).
207+
208+
Example ``defaults.json`` file:
209+
210+
{
211+
"contenttype": "application/xml",
212+
"charset": "ascii"
213+
}
214+
215+
If ``contenttype`` and/or ``charset`` is set on an individual request creator, it will override the defaults. Defaults defined on the endpoint level
216+
overrides defaults on the endpoint collection level.
217+
218+
If no defaults are used, the default for ``charset`` is utf-8. There is no default for ``contenttype``. See also the section
219+
"HTTP Response encoding and the Content-Type header".
220+
200221

201222
### Encodings
202223

@@ -211,16 +232,16 @@ for more information.
211232
#### HTTP Response encoding and the Content-Type header
212233

213234
* The ``charset`` parameter determines the response encoding for netmockery responses (expect for forwarded external requests).
214-
* If no charset parameter is specified, netmockery uses ISO-8859-1 (latin1) encoding.
235+
* If no charset parameter is specified, netmockery uses UTF-8 encoding.
215236
* The Content-Type header for the responses is set in this manner:
216237
* If ``contenttype`` is NOT set, no ``Content-Type`` header is set for the responses
217238
* If ``contenttype`` is set to ``foo/bar`` and ``charset`` is NOT set
218-
1. netmockery encodes the response using the ISO-8859-1 encoding
219-
2. ``Content-Type`` = ``foo/bar; charset=iso-8859-1``
220-
* If ``contenttype`` is set to ``foo/bar`` and ``charset`` is set to one of the supported encodings (see list below)
221-
1. netmockery encodes the response using the specified encoding (eg. ``utf-8``)
239+
1. netmockery encodes the response using the UTF-8 encoding
222240
2. ``Content-Type`` = ``foo/bar; charset=utf-8``
223-
* For forwarded external requests, not encoding and content-type handling is done.
241+
* If ``contenttype`` is set to ``foo/bar`` and ``charset`` is set to one of the supported encodings (see list below)
242+
1. netmockery encodes the response using the specified encoding (eg. ``iso-8859-1``)
243+
2. ``Content-Type`` = ``foo/bar; charset=iso-8859-1``
244+
* For forwarded external requests, no encoding and content-type handling is done.
224245

225246
#### Valid charset names (not case sensitive)
226247

0 commit comments

Comments
 (0)