Compare commits

..
9 Commits
Author SHA1 Message Date
Martin fee03021b1 Include Tims regexs to extract Class And Stick
SuccessfulCode/BritishHorseRacingApi/pipeline/head There was a failure building this commit
Successfulcode/BritishHorseRacingApi/pipeline/head There was a failure building this commit
2020-05-10 22:42:46 +01:00
Martin a2e23ff955 DeclaredTime can be null
SuccessfulCode/BritishHorseRacingApi/master There was a failure building this commit
SuccessfulCode/BritishHorseRacingApi/pipeline/head There was a failure building this commit
2020-04-20 08:46:24 +01:00
Martin 43ad66e703 Update tools
SuccessfulCode/BritishHorseRacingApi/master This commit looks good
2020-04-15 09:06:01 +01:00
Martin faca4ddd6e Enable larger http requests
SuccessfulCode/BritishHorseRacingApi/master Something is wrong with the build of this commit
2020-04-14 09:11:04 +01:00
Martin c082d1295c Up fixtures to 100 to prevent excess paging
SuccessfulCode/BritishHorseRacingApi/master This commit looks good
2020-04-13 19:06:33 +01:00
Martin 6da6a9de4b New guid on every request
SuccessfulCode/BritishHorseRacingApi/master This commit looks good
2020-04-13 16:53:47 +01:00
Martin 1bdc303c38 JockeyId can be null
SuccessfulCode/BritishHorseRacingApi/master This commit looks good
2020-04-13 12:31:21 +01:00
Martin b851cde957 Throw exceptions instead of trying to recover
SuccessfulCode/BritishHorseRacingApi/master This commit looks good
2020-04-12 18:01:14 +01:00
Martin b4cf9d0056 Add guid to token
SuccessfulCode/BritishHorseRacingApi/master This commit looks good
2020-04-12 10:07:37 +01:00
11 changed files with 150 additions and 36 deletions
+13 -8
View File
@@ -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)]
+18 -22
View File
@@ -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));
response = await SendRequest(endpoint);
content = response.Content;
continue;
}
await Task.Delay(TimeSpan.FromSeconds(_waitTimeAfterFail));
if (content.StartsWith("<"))
await GetNewToken();
response = await SendRequest(endpoint);
content = response.Content;
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))
+4
View File
@@ -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; }
+1 -1
View File
@@ -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; }
+2 -1
View File
@@ -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);
}
}
+1 -1
View File
@@ -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; }