Compare commits
9
Commits
05856e081a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fee03021b1 | ||
|
|
a2e23ff955 | ||
|
|
43ad66e703 | ||
|
|
faca4ddd6e | ||
|
|
c082d1295c | ||
|
|
6da6a9de4b | ||
|
|
1bdc303c38 | ||
|
|
b851cde957 | ||
|
|
b4cf9d0056 |
@@ -4,11 +4,14 @@ using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
using SuccessfulCodeTools.LowLevelHttpClient;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BritishHorseRacingApi.Tests
|
||||
{
|
||||
public class ApiClientTests
|
||||
{
|
||||
public static string _cookie = "D_ZID=3F37C446-48FB-3F85-B8D2-66FC90A17473; D_ZUID=27B27203-7708-3F5E-8C01-86A8BF3A21DD; D_HID=BC00CAA4-06EE-39E0-8579-A00DCFA20171; D_SID=188.172.158.138:JT/OEx87nl5ScspTQk38UsDfuIz94DbCf5CYsIC7WpU; bha-ie-test=true; __utmc=36595059; __utmz=36595059.1586780062.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); D_IID=A18DB74E-9ABD-33E1-BF94-96209AF6178E; D_UID=9CDC96FD-4EFE-308E-BC28-2F105AC0B01B; bha_token=%7B%22token%22%3A%22NmZjNDgzNTI0YmFlYzM5MmJkN2U4N2E2MTg2YjVlMWY%3D%22%2C%22timestamp%22%3A1586811155132%7D; __utma=36595059.39554265.1586780062.1586811153.1586848611.7; __utmt=1; __utmb=36595059.1.10.1586848611";
|
||||
|
||||
[Fact]
|
||||
public async Task GetToken()
|
||||
{
|
||||
@@ -40,7 +43,7 @@ namespace BritishHorseRacingApi.Tests
|
||||
public async Task GetRaceResult()
|
||||
{
|
||||
//Arrange
|
||||
var client = new ApiClient();
|
||||
var client = new ApiClient(cookie: _cookie);
|
||||
var token = await client.GetNewToken();
|
||||
//Act
|
||||
var result = await client.GetRaceResults(2018, 42638, 0);
|
||||
@@ -54,13 +57,13 @@ namespace BritishHorseRacingApi.Tests
|
||||
public async Task GetFixtures()
|
||||
{
|
||||
//Arrange
|
||||
var client = new ApiClient();
|
||||
var client = new ApiClient(cookie: _cookie);
|
||||
var token = await client.GetNewToken();
|
||||
//Act
|
||||
var result = await client.GetFixtures(2019, 2);
|
||||
//Assert
|
||||
Assert.Equal(111, result.Total);
|
||||
Assert.Equal(10, result.Data.Length);
|
||||
Assert.Equal(100, result.Data.Length);
|
||||
Assert.Equal(18142, result.Data[0].FixtureId);
|
||||
}
|
||||
|
||||
@@ -85,13 +88,15 @@ namespace BritishHorseRacingApi.Tests
|
||||
Assert.True(results.Count > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRaceRunners()
|
||||
[Theory]
|
||||
[InlineData(2020, 31967, 0, 4)]
|
||||
[InlineData(2019, 16974, 0, 18)]
|
||||
public async Task GetRaceRunners(int year, int raceId, int divisionSequence, int expectedRunnerCount)
|
||||
{
|
||||
var client = new ApiClient();
|
||||
var client = new ApiClient(cookie: _cookie);
|
||||
var token = await client.GetNewToken();
|
||||
var results = await client.GetRaceRunners(2020, 31967, 0);
|
||||
Assert.Equal(4, results.Data.Length);
|
||||
var results = await client.GetRaceRunners(year, raceId, divisionSequence);
|
||||
Assert.Equal(expectedRunnerCount, results.Data.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="SuccessfulCodeTools" Version="1.0.8" />
|
||||
<PackageReference Include="SuccessfulCodeTools" Version="1.0.10" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.0.1" />
|
||||
|
||||
@@ -47,6 +47,18 @@ namespace BritishHorseRacingApi.Tests
|
||||
//Assert
|
||||
Assert.Equal(totalMilliseconds, expectedValue);
|
||||
}
|
||||
[Theory]
|
||||
[InlineData("(GoingStick:5.3 on 01-12-2018 at 08:30) Hurdle 5.3 Chase 5.3 @ 0800", 5.3)]
|
||||
[InlineData("ABANDONED - Abandoned (48 Hours)", null)]
|
||||
[InlineData("", null)]
|
||||
[InlineData("Chase - Good to Soft, Good in Places Hurdle - Good to Soft (Hurdle races on Outer/Flat course) Chase 7.5; Hurdle 7.1", 7.3)]
|
||||
public void TestGoingStickExtraction(string goingStr, double? expectedValue)
|
||||
{
|
||||
//Act
|
||||
var result = DataConverterHelper.ExtractGoingStick(goingStr);
|
||||
//Assert
|
||||
Assert.Equal(expectedValue, result);
|
||||
}
|
||||
|
||||
[Theory()]
|
||||
[InlineData("1m 1f 104y ", 2084)]
|
||||
@@ -64,6 +76,19 @@ namespace BritishHorseRacingApi.Tests
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", null)]
|
||||
[InlineData(null, null)]
|
||||
[InlineData("THE 32Red CASINO NOVICE AUCTION STAKES (CLASS 5)", 5)]
|
||||
[InlineData("THE BRITISH STALLION STUDS EBF KEGWORTH NOVICE STAKES (CLASS 4) (PLUS 10 RACE) (DIV II)", 4)]
|
||||
public void TestRaceClassExtract(string raceName, int? expectedResult)
|
||||
{
|
||||
//Act
|
||||
var result = DataConverterHelper.ExtractRaceClass(raceName);
|
||||
//Assert
|
||||
Assert.Equal(expectedResult, result);
|
||||
}
|
||||
|
||||
[Theory()]
|
||||
[InlineData("3/5", 1.6)]
|
||||
[InlineData("20/1", 21)]
|
||||
|
||||
@@ -47,11 +47,11 @@ namespace BritishHorseRacingApi
|
||||
var response = await _client.SendAsync(message);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new Exception("Token not retrieved");
|
||||
throw new ApiResponseException("Token not retrieved:" + response.Content);
|
||||
}
|
||||
|
||||
var content = response.Content;
|
||||
while (content.StartsWith("<") || content.StartsWith("Error: \"Failed to connect"))
|
||||
if (content.StartsWith("<") || content.StartsWith("Error: \"Failed to connect"))
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(_waitTimeAfterFail));
|
||||
if (content.StartsWith("<"))
|
||||
@@ -59,15 +59,12 @@ namespace BritishHorseRacingApi
|
||||
_client.DefaultHeaders.Items.Clear();
|
||||
_client.OutgoingPort++;
|
||||
message = CreateTokenMessage();
|
||||
message.Headers.Items.RemoveAll(e => e.Key == "Accept-Language");
|
||||
message.Headers.Items.Add(new KeyValuePair<string, string>("Accept-Language", $"en-US,en;q=0.9;id={Guid.NewGuid().ToString()}"));
|
||||
}
|
||||
response = await _client.SendAsync(message);
|
||||
content = response.Content;
|
||||
if (content.StartsWith("Error: \"Failed to connect"))
|
||||
if (content.StartsWith("<") || content.StartsWith("Error: \"Failed to connect"))
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
Trace.WriteLine("Waiting after conntect fail");
|
||||
throw new ApiResponseException("Failed to get token. Response: " + content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +79,7 @@ namespace BritishHorseRacingApi
|
||||
var message = new CustomHttpRequestMessage(HttpMethod.Get, "http://www.britishhorseracing.com/feeds/v1/token/request");
|
||||
|
||||
message.Headers.Add("Accept", "application/json, text/plain, */*");
|
||||
message.Headers.Add("Accept-Language", "en-US,en;q=0.9");
|
||||
message.Headers.Add("Accept-Language", $"en-US,en;q=0.9;id={Guid.NewGuid()}");
|
||||
message.Headers.Add("User-Agent", _userAgent);
|
||||
message.Headers.Add("Host", "www.britishhorseracing.com");
|
||||
message.Headers.Add("cache-control", "no-cache");
|
||||
@@ -219,7 +216,7 @@ namespace BritishHorseRacingApi
|
||||
return fixtures;
|
||||
}
|
||||
|
||||
public async Task<PagedResult<Fixture>> GetFixtures(int? year = null, int? month = null, int page = 1, int perPage = 10, string order = "desc", bool? resultsAvailable = true)
|
||||
public async Task<PagedResult<Fixture>> GetFixtures(int? year = null, int? month = null, int page = 1, int perPage = 100, string order = "desc", bool? resultsAvailable = true)
|
||||
{
|
||||
var uriBuilder = new UriBuilder(Endpoints.Fixtures);
|
||||
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||
@@ -262,21 +259,21 @@ namespace BritishHorseRacingApi
|
||||
}
|
||||
|
||||
var content = response.Content;
|
||||
while (content.StartsWith("<") || content.StartsWith("Error: \"Failed to connect"))
|
||||
if(content.StartsWith("<") || content.StartsWith("Error: \"Failed to connect"))
|
||||
{
|
||||
Trace.WriteLine("Content not valid requerying. Content: "+ content);
|
||||
if (!content.StartsWith("<"))
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
await Task.Delay(TimeSpan.FromSeconds(_waitTimeAfterFail));
|
||||
|
||||
if (content.StartsWith("<"))
|
||||
await GetNewToken();
|
||||
|
||||
response = await SendRequest(endpoint);
|
||||
content = response.Content;
|
||||
continue;
|
||||
}
|
||||
if (content.StartsWith("<") || content.StartsWith("Error: \"Failed to connect"))
|
||||
throw new ApiResponseException($"Error in response: {content}");
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(_waitTimeAfterFail));
|
||||
await GetNewToken();
|
||||
_client.DefaultHeaders.Items.RemoveAll(e => e.Key == "Accept-Language");
|
||||
_client.DefaultHeaders.Items.Add(new KeyValuePair<string, string>("Accept-Language", $"en-US,en;q=0.9;id={Guid.NewGuid().ToString()}"));
|
||||
_client.OutgoingPort++;
|
||||
response = await SendRequest(endpoint);
|
||||
content = response.Content;
|
||||
@@ -285,7 +282,7 @@ namespace BritishHorseRacingApi
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new Exception($"Unexpected error: {content}" );
|
||||
throw new ApiResponseException($"Unexpected error: {content}" );
|
||||
}
|
||||
|
||||
if(preDeserilizationValidation != null && !preDeserilizationValidation(JToken.Parse(content)))
|
||||
@@ -308,8 +305,7 @@ namespace BritishHorseRacingApi
|
||||
message.Headers.Add("Authorization", CurrentToken);
|
||||
message.Headers.Add("Accept", "application/json");
|
||||
message.Headers.Add("accept-encoding", "gzip, deflate");
|
||||
if(!_client.DefaultHeaders.Items.Any(e => e.Key == "Accept-Language"))
|
||||
message.Headers.Add("Accept-Language", "en-US,en;q=0.9");
|
||||
message.Headers.Add("Accept-Language", $"en-US,en;q=0.9;id={Guid.NewGuid()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace BritishHorseRacingApi
|
||||
{
|
||||
[Serializable]
|
||||
internal class ApiResponseException : Exception
|
||||
{
|
||||
public ApiResponseException()
|
||||
{
|
||||
}
|
||||
|
||||
public ApiResponseException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ApiResponseException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected ApiResponseException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SuccessfulCodeTools" Version="1.0.8" />
|
||||
<PackageReference Include="SuccessfulCodeTools" Version="1.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
@@ -28,6 +29,47 @@ namespace BritishHorseRacingApi.Helpers
|
||||
("furlongs", "f"),
|
||||
("yards", "y")), RegexOptions.Compiled);
|
||||
|
||||
|
||||
private static Regex _raceClassExtractorRegex = new Regex(@"\(class([^)]*)\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public static int? ExtractRaceClass(string raceName)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(raceName))
|
||||
return null;
|
||||
|
||||
var match = _raceClassExtractorRegex.Match(raceName);
|
||||
if (!match.Success || match.Groups.Count < 1)
|
||||
return null;
|
||||
|
||||
var raceClassString = match.Groups[1].Value;
|
||||
if (!int.TryParse(raceClassString, out int raceClassInt))
|
||||
return null;
|
||||
|
||||
return raceClassInt;
|
||||
}
|
||||
|
||||
public static string GetGoingCategory(double? goingStick)
|
||||
{
|
||||
if (goingStick == null)
|
||||
return null;
|
||||
|
||||
if (goingStick < 6.4)
|
||||
return "heavy";
|
||||
|
||||
if (goingStick < 7.1)
|
||||
return "soft";
|
||||
|
||||
if (goingStick < 7.9)
|
||||
return "good-soft";
|
||||
|
||||
if (goingStick < 8.6)
|
||||
return "good";
|
||||
|
||||
if (goingStick < 9.9)
|
||||
return "good-firm";
|
||||
|
||||
return "firm";
|
||||
}
|
||||
|
||||
public static int? DistanceStringToYards(string distance)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(distance))
|
||||
@@ -42,6 +84,20 @@ namespace BritishHorseRacingApi.Helpers
|
||||
return totalYards;
|
||||
}
|
||||
|
||||
public static Regex _goingStickRegexExtractor = new Regex(@"(\d+)(?:\.(\d{1,2}))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
public static double? ExtractGoingStick(string going)
|
||||
{
|
||||
var matches = _goingStickRegexExtractor.Matches(going);
|
||||
var goingStickResults = matches.OfType<Match>()
|
||||
.Where(e => e.Value.Contains("."))
|
||||
.Select(e => Convert.ToSingle(e.Value))
|
||||
.ToList();
|
||||
|
||||
if (goingStickResults.Count == 0)
|
||||
return null;
|
||||
|
||||
return Math.Round(goingStickResults.Average(), 2);
|
||||
}
|
||||
private static string BuildExtractComponentsRegex(params (string label, string indicator)[] pairs)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
@@ -68,6 +124,8 @@ namespace BritishHorseRacingApi.Helpers
|
||||
return (int)TimeSpan.FromSeconds(lossDifference).TotalMilliseconds;
|
||||
}
|
||||
private static Regex _ratioRegex = new Regex("(?'Numerator'[0-9.]+)/(?'Denominator'[0-9.]+)", RegexOptions.Compiled);
|
||||
|
||||
|
||||
public static decimal? FractionOddsToDecimal(string fraction)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(fraction))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using BritishHorseRacingApi.Helpers;
|
||||
using System;
|
||||
|
||||
namespace BritishHorseRacingApi.Models
|
||||
@@ -25,6 +26,9 @@ namespace BritishHorseRacingApi.Models
|
||||
public string TicketsLink { get; set; }
|
||||
public int? MajorEvent { get; set; }
|
||||
public string Going { get; set; }
|
||||
public double? GoingStick() => DataConverterHelper.ExtractGoingStick(Going);
|
||||
public string GoingCatergory => DataConverterHelper.GetGoingCategory(GoingStick());
|
||||
|
||||
public string Weather { get; set; }
|
||||
public string Other { get; set; }
|
||||
public string Stalls { get; set; }
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace BritishHorseRacingApi.Models
|
||||
public DateTime? DeclaredDate { get; set; }
|
||||
|
||||
[JsonProperty("declaredTime")]
|
||||
public TimeSpan DeclaredTime { get; set; }
|
||||
public TimeSpan? DeclaredTime { get; set; }
|
||||
|
||||
[JsonProperty("declaredReason")]
|
||||
public string DeclaredReason { get; set; }
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace BritishHorseRacingApi.Models
|
||||
public string AgeLimit { get; set; }
|
||||
public int PrizeAmount { get; set; }
|
||||
public string PrizeCurrency { get; set; }
|
||||
public int RaceClass { get; set; }
|
||||
public int? RaceClass { get; set; }
|
||||
public string RatingBand { get; set; }
|
||||
public string RawDistanceText { get; set; }
|
||||
public string DistanceUnits { get; set; }
|
||||
@@ -32,5 +32,6 @@ namespace BritishHorseRacingApi.Models
|
||||
public Regex _distanceExtractorRegex = new Regex(@"(?'miles'[0-9\.]*(?=m))?m? ?(?'furlongs'[0-9\.]*(?=f))?f? ?.*?(?'yards'[0-9\.]*(?=y))", RegexOptions.Compiled);
|
||||
|
||||
public int? DistanceInYards() => DataConverterHelper.DistanceStringToYards(DistanceText);
|
||||
public int? RaceClassFromName() => DataConverterHelper.ExtractRaceClass(RaceName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace BritishHorseRacingApi.Models
|
||||
public Status Status { get; set; }
|
||||
|
||||
[JsonProperty("jockeyId")]
|
||||
public int JockeyId { get; set; }
|
||||
public int? JockeyId { get; set; }
|
||||
|
||||
[JsonProperty("jockeyName")]
|
||||
public string JockeyName { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user