Skip to content

Commit

Permalink
Support localized faker data generation based on "Accept-Language" re…
Browse files Browse the repository at this point in the history
…quest header, fix #6
  • Loading branch information
natenho committed Aug 11, 2019
1 parent cb664d4 commit 7776ccc
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 19 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -369,5 +369,6 @@ The code tag structure resembles [T4 Text Template Engine](https://github.com/mo
```

The built-in fake data is generated via [Bogus](https://github.com/bchavez/Bogus).
The faker can also generate localized data using ```Accept-Language``` HTTP header. Defaults to ```en``` (english) fake data.

Icon made by [Freepik](https://www.freepik.com/ "Freepik") from [www.flaticon.com](https://www.flaticon.com/ "Flaticon") is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/ "Creative Commons BY 3.0")
14 changes: 14 additions & 0 deletions src/Mockaco/Extensions/HttpRequestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
using Mockaco;
using Mockaco.Routing;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

Expand Down Expand Up @@ -115,5 +117,17 @@ public static string ReadBodyStream(this HttpRequest httpRequest)

return body;
}

public static IEnumerable<string> GetAcceptLanguageValues(this HttpRequest httpRequest)
{
var acceptLanguages = httpRequest.GetTypedHeaders().AcceptLanguage;

if(acceptLanguages == default)
{
return Enumerable.Empty<string>();
}

return acceptLanguages?.Select(l => l.Value.ToString());
}
}
}
1 change: 1 addition & 0 deletions src/Mockaco/Http/HttpHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public static class HttpHeaders
{
public const string ContentType = "Content-Type";
public const string AcceptLanguage = "Accept-Language";
}
}
1 change: 0 additions & 1 deletion src/Mockaco/Models/ResponseTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public class ResponseTemplate
public ResponseTemplate()
{
Headers = new StringDictionary();
Body = JToken.Parse(string.Empty);
}
}
}
24 changes: 16 additions & 8 deletions src/Mockaco/Routing/RouteProvider.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
using Microsoft.Extensions.Logging;
using Bogus;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Mockaco.Processors;
using Mono.TextTemplating;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Threading.Tasks;

Expand All @@ -13,19 +18,22 @@ namespace Mockaco.Routing
public class RouteProvider : IRouteProvider
{
private List<Route> _cache;
private readonly IScriptContext _scriptContext;
private readonly IFakerFactory _fakerFactory;
private readonly ITemplateProvider _templateProvider;
private readonly ITemplateTransformer _templateTransformer;
private readonly ILogger<RouteProvider> _logger;

public RouteProvider(IScriptContext scriptContext, ITemplateProvider templateProvider, ITemplateTransformer templateTransformer, ILogger<RouteProvider> logger)
public RouteProvider(IFakerFactory fakerFactory, ITemplateProvider templateProvider, ITemplateTransformer templateTransformer, ILogger<RouteProvider> logger)
{
_cache = new List<Route>();
_scriptContext = scriptContext;

_fakerFactory = fakerFactory;

_templateProvider = templateProvider;
_templateProvider.OnChange += TemplateProviderChange;

_templateTransformer = templateTransformer;

_logger = logger;
}

Expand All @@ -41,10 +49,10 @@ public List<Route> GetRoutes()

public async Task WarmUp()
{
var blankScriptContext = _scriptContext;

var stopwatch = Stopwatch.StartNew();

var nullScriptContext = new ScriptContext(_fakerFactory);

const int defaultCapacity = 16;
var routes = new List<Route>(_cache.Count > 0 ? _cache.Count : defaultCapacity);

Expand All @@ -57,15 +65,15 @@ public async Task WarmUp()
if (cachedRoute != null)
{
_logger.LogInformation("Using cached {0} ({1})", rawTemplate.Name, rawTemplate.Hash);

routes.Add(cachedRoute);

continue;
}

_logger.LogInformation("Loading {0} ({1})", rawTemplate.Name, rawTemplate.Hash);

var template = await _templateTransformer.Transform(rawTemplate, blankScriptContext);
var template = await _templateTransformer.Transform(rawTemplate, nullScriptContext);

routes.Add(new Route(template.Request.Method, template.Request.Route, rawTemplate, template.Request.Condition.HasValue));

Expand Down
73 changes: 73 additions & 0 deletions src/Mockaco/Scripting/HttpRequestFakerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Bogus;
using Bogus.Extensions;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace Mockaco
{
public class HttpRequestFakerFactory : IFakerFactory
{
private readonly ILogger<HttpRequestFakerFactory> _logger;

public HttpRequestFakerFactory(ILogger<HttpRequestFakerFactory> logger)
{
_logger = logger;
}

public Faker GetDefaultFaker()
{
var currentCultureBogusLocale = CultureInfo.DefaultThreadCurrentCulture?.ToBogusLocale();

if (currentCultureBogusLocale != null)
{
return new Faker(currentCultureBogusLocale);
}

return new Faker();
}

public Faker GetFaker(IEnumerable<string> acceptLanguages)
{
var supportedBogusLocales = GetSupportedBogusLocales(acceptLanguages);

var firstSupportedBogusLocale = supportedBogusLocales.FirstOrDefault();

if (firstSupportedBogusLocale == default)
{
return GetDefaultFaker();
}

return new Faker(firstSupportedBogusLocale);
}

private IEnumerable<string> GetSupportedBogusLocales(IEnumerable<string> acceptLanguages)
{
var bogusLocales = new List<string>();

foreach (var acceptLanguage in acceptLanguages)
{
try
{
var cultureInfo = CultureInfo.GetCultureInfo(acceptLanguage);

var bogusLocale = cultureInfo.ToBogusLocale();

if (bogusLocale == default)
{
_logger.LogWarning("Accept-Language not supported by Bogus: {language}", acceptLanguage);
}

bogusLocales.Add(bogusLocale);
}
catch (CultureNotFoundException)
{
_logger.LogWarning("Accept-Language not supported: {language}", acceptLanguage);
}
}

return bogusLocales;
}
}
}
12 changes: 12 additions & 0 deletions src/Mockaco/Scripting/IFakerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Bogus;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;

namespace Mockaco
{
public interface IFakerFactory
{
Faker GetDefaultFaker();
Faker GetFaker(IEnumerable<string> acceptLanguages);
}
}
19 changes: 12 additions & 7 deletions src/Mockaco/Scripting/ScriptContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,28 @@ public class ScriptContext : IScriptContext
private StringDictionary _headersDictionary = new StringDictionary();
private StringDictionary _routeDictionary = new StringDictionary();
private JObject _parsedBody;

public Faker Faker { get; }
private readonly IFakerFactory _fakerFactory;

public Faker Faker { get; private set; }

public ScriptContextRequest Request { get; set; }

public ScriptContextResponse Response { get; set; }

public ScriptContext()
public ScriptContext(IFakerFactory fakerFactory)
{
Faker = new Faker("pt_BR"); // TODO Localize based on the request

_fakerFactory = fakerFactory;

Faker = fakerFactory?.GetDefaultFaker();

Request = new ScriptContextRequest(
default,
new StringDictionary(),
new StringDictionary(),
new StringDictionary(),
new JObject());

Response = new ScriptContextResponse(new StringDictionary(), new JObject());
Response = new ScriptContextResponse(new StringDictionary(), new JObject());
}

public void AttachRequest(HttpRequest httpRequest)
Expand All @@ -45,6 +48,8 @@ public void AttachRequest(HttpRequest httpRequest)
_headersDictionary = httpRequest.Headers.ToStringDictionary(k => k.Key, v => v.Value.ToString());
_parsedBody = ParseBody(httpRequest);

Faker = _fakerFactory?.GetFaker(httpRequest.GetAcceptLanguageValues());

Request = new ScriptContextRequest(url: _uri, route: _routeDictionary, query: _queryDictionary, header: _headersDictionary, body: _parsedBody);
}

Expand Down
7 changes: 4 additions & 3 deletions src/Mockaco/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ public void ConfigureServices(IServiceCollection services)
services.AddMemoryCache();
services.AddHttpClient();

services.AddScoped<IMockacoContext, MockacoContext>();
services.AddScoped<IScriptContext, ScriptContext>();
services.AddScoped<IMockacoContext, MockacoContext>();
services.AddScoped<IScriptContext, ScriptContext>();

services.AddSingleton<IScriptRunnerFactory, ScriptRunnerFactory>();

services.AddSingleton<IFakerFactory, HttpRequestFakerFactory>();
services.AddSingleton<IRouteProvider, RouteProvider>();
services.AddSingleton<ITemplateProvider, TemplateFileProvider>();

Expand Down
1 change: 1 addition & 0 deletions test/Mockaco.Tests/Mockaco.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.8.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Moq" Version="4.12.0" />
Expand Down
34 changes: 34 additions & 0 deletions test/Mockaco.Tests/Scripting/HttpRequestFakerFactoryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
using System.Linq;
using Xunit;

namespace Mockaco.Tests.Scripting
{
public class HttpRequestFakerFactoryTest
{
private HttpRequestFakerFactory _httpRequestFakerFactory;

public HttpRequestFakerFactoryTest()
{
_httpRequestFakerFactory = new HttpRequestFakerFactory(Mock.Of<ILogger<HttpRequestFakerFactory>>());
}

[Fact]
public void Gets_Localized_Faker_Based_On_Http_Accept_Language_Header()
{
var faker = _httpRequestFakerFactory.GetFaker(new[] { "pt-BR" });

faker.Locale.Should().Be("pt_BR");
}

[Fact]
public void Gets_Default_Faker_When_No_Accept_Language_Header_Is_Present()
{
var faker = _httpRequestFakerFactory.GetFaker(Enumerable.Empty<string>());

faker.Locale.Should().Be("en");
}
}
}

0 comments on commit 7776ccc

Please sign in to comment.