Skip to content

Commit

Permalink
Merge pull request #34 from skuill/feature/28_lyricfind_region_availa…
Browse files Browse the repository at this point in the history
…bility

Feature/28 lyricfind region availability
  • Loading branch information
skuill authored Dec 13, 2024
2 parents c3a9140 + 25d9ecb commit 8d94e60
Show file tree
Hide file tree
Showing 18 changed files with 262 additions and 61 deletions.
54 changes: 54 additions & 0 deletions LyricsScraperNET.TestShared/Attributes/RegionalTestAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Globalization;
using System.Linq;
using Xunit;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class RegionalTestTheoryAttribute : TheoryAttribute
{
public RegionalTestTheoryAttribute(string[] includeRegions = null, string[] excludeRegions = null)
{
if (!ShouldRunTest(includeRegions, excludeRegions))
{
Skip = GenerateSkipMessage(includeRegions, excludeRegions);
}
}

private static bool ShouldRunTest(string[] includeRegions, string[] excludeRegions)
{
var currentRegion = RegionInfo.CurrentRegion.Name;

// If regions are specified to be included, run only if the current region is in the list.
if (includeRegions != null && includeRegions.Length > 0)
{
if (!includeRegions.Any(code => string.Equals(currentRegion, code, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}

// If regions are specified to be excluded, do not run if the current region is in the list.
if (excludeRegions != null && excludeRegions.Length > 0)
{
if (excludeRegions.Any(code => string.Equals(currentRegion, code, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}

return true;
}

private static string GenerateSkipMessage(string[] includeRegions, string[] excludeRegions)
{
var includeMessage = includeRegions != null && includeRegions.Length > 0
? $"Include: {string.Join(", ", includeRegions)}."
: string.Empty;

var excludeMessage = excludeRegions != null && excludeRegions.Length > 0
? $"Exclude: {string.Join(", ", excludeRegions)}."
: string.Empty;

return $"Test skipped. {includeMessage} {excludeMessage}".Trim();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="8.3.0" />
<PackageReference Include="System.Text.Json" Version="6.0.11" />
<PackageReference Include="xunit" Version="2.9.2" />
</ItemGroup>

<ItemGroup>
Expand Down
7 changes: 6 additions & 1 deletion LyricsScraperNET/Models/Responses/ResponseStatusCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public enum ResponseStatusCode
/// <summary>
/// The search request is incorrect or contains malformed data.
/// </summary>
BadRequest = 4
BadRequest = 4,

/// <summary>
/// Lyrics are unavailable due to regional restrictions.
/// </summary>
RegionRestricted = 5
}
}
6 changes: 6 additions & 0 deletions LyricsScraperNET/Models/Responses/SearchResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ internal SearchResult(ExternalProviderType externalProviderType)
ExternalProviderType = externalProviderType;
}

internal SearchResult(ExternalProviderType externalProviderType, ResponseStatusCode responseStatusCode)
: this(externalProviderType)
{
ResponseStatusCode = responseStatusCode;
}

internal SearchResult(string lyricText, ExternalProviderType externalProviderType)
: this(externalProviderType)
{
Expand Down
2 changes: 1 addition & 1 deletion LyricsScraperNET/Network/HtmlAgilityWebClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public HtmlAgilityWebClient()
{
}

public HtmlAgilityWebClient(ILogger<HtmlAgilityWebClient> logger)
public HtmlAgilityWebClient(ILogger<HtmlAgilityWebClient> logger) : this()
{
_logger = logger;
}
Expand Down
31 changes: 10 additions & 21 deletions LyricsScraperNET/Network/NetHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,49 +14,38 @@ public NetHttpClient()
{
}

public NetHttpClient(ILogger<NetHttpClient> logger)
public NetHttpClient(ILogger<NetHttpClient> logger) : this()
{
_logger = logger;
}

public string Load(Uri uri)
{
var httpClient = new HttpClient();
string htmlPageBody;

try
{
htmlPageBody = httpClient.GetStringAsync(uri).GetAwaiter().GetResult();
}
catch (HttpRequestException ex)
{
_logger?.LogWarning($"HttpClient GetStringAsync throw exception for uri: {uri}. Exception: {ex}");
return string.Empty;
}

CheckResult(htmlPageBody, uri);

return htmlPageBody;
return LoadAsync(uri).GetAwaiter().GetResult();
}

public async Task<string> LoadAsync(Uri uri)
{
var httpClient = new HttpClient();
string htmlPageBody;
var request = new HttpRequestMessage(HttpMethod.Get, uri);

HttpResponseMessage response;
try
{
htmlPageBody = await httpClient.GetStringAsync(uri);
response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex)
{
_logger?.LogWarning($"HttpClient GetStringAsync throw exception for uri: {uri}. Exception: {ex}");
return string.Empty;
}

CheckResult(htmlPageBody, uri);
var htmlContent = await response.Content.ReadAsStringAsync();

CheckResult(htmlContent, uri);

return htmlPageBody;
return htmlContent;
}

private void CheckResult(string? result, Uri uri)
Expand Down
24 changes: 22 additions & 2 deletions LyricsScraperNET/Providers/LyricFind/LyricFindProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace LyricsScraperNET.Providers.LyricFind
Expand All @@ -25,6 +26,11 @@ public sealed class LyricFindProvider : ExternalProviderBase
// "songIsInstrumental":true
private const string _songIsInstrumentalStart = "\"songIsInstrumental\"";

// "viewable":false
private const string _viewableStart = "\"viewable\"";
// "response":{"code":206,"description":"LYRIC NOT AVAILABLE: BLOCKED"}
private const string _lyricNotAvailablePattern = @"\{[^}]*(""response""\s*:\s*\{[^}]*(""code""\s*:\s*206)[^}]*(""description""\s*:\s*""LYRIC NOT AVAILABLE: BLOCKED"")[^}]*\})[^}]*\}|\{[^}]*(""response""\s*:\s*\{[^}]*(""description""\s*:\s*""LYRIC NOT AVAILABLE: BLOCKED"")[^}]*(""code""\s*:\s*206)[^}]*\})[^}]*\}";

#region Constructors

public LyricFindProvider()
Expand Down Expand Up @@ -124,6 +130,10 @@ private SearchResult PostProcessLyric(Uri uri, string text)
if (IsInstumentalLyric(text))
return new SearchResult(Models.ExternalProviderType.LyricFind).AddInstrumental(true);

// In case if lyrics existed, but "These lyrics are not available in your region currently."
if (IsRegionRestrictedLyric(text))
return new SearchResult(Models.ExternalProviderType.LyricFind, ResponseStatusCode.RegionRestricted);

_logger?.LogWarning($"LyricFind. Can't find lyrics for Uri: [{uri}]");
return new SearchResult(Models.ExternalProviderType.LyricFind);
}
Expand Down Expand Up @@ -152,6 +162,16 @@ private SearchResult PostProcessLyric(Uri uri, string text)
return new SearchResult(result, Models.ExternalProviderType.LyricFind);
}


/// <summary>
/// Check if lyric text contains region restricted information like viewable (false) and repsonse with code (206) and description.
/// </summary>
private bool IsRegionRestrictedLyric(string text)
{
return TryReturnBooleanFieldValue(text, _viewableStart, "false")
&& Regex.IsMatch(text, _lyricNotAvailablePattern); ;
}

/// <summary>
/// Check if lyric text contains instrumental flag.
/// </summary>
Expand All @@ -165,13 +185,13 @@ private bool IsInstumentalLyric(string text)
/// Try to find and return the fielad value as boolean. Pattern: [<paramref name="fieldName"/>:true(or false)].
/// In case if fieldName is not found returns false.
/// </summary>
private bool TryReturnBooleanFieldValue(string text, string fieldName)
private bool TryReturnBooleanFieldValue(string text, string fieldName, string booleanValue = "true")
{
var startIndex = text.IndexOf(fieldName);
if (startIndex <= 0)
return false;
var fieldValue = text.Substring(startIndex + fieldName.Length + 1, 5);
return fieldValue.IndexOf("true", StringComparison.OrdinalIgnoreCase) >= 0;
return fieldValue.IndexOf(booleanValue, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@
<None Update="Providers\Genius\Resources\Lyrics_Result_02.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Providers\LyricFind\instrumental_test_data.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Providers\LyricFind\Resources\Lyrics_Result_01.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand All @@ -67,9 +64,6 @@
<None Update="Providers\AZLyrics\lyric_test_data.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Providers\SongLyrics\instrumental_test_data.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Providers\SongLyrics\Resources\Lyrics_Result_02.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ public void SearchLyric_IntegrationDynamicData_Success(LyricsTestData testData)
Assert.False(searchResult.Instrumental);
}

[Theory]
[MemberData(nameof(GetTestData), parameters: "Providers\\LyricFind\\instrumental_test_data.json")]
public void SearchLyric_IntegrationDynamicData_Instrumental(LyricsTestData testData)
[RegionalTestTheory(excludeRegions: new[] { "AM", "RU" })]
[InlineData("rush", "yyz")]
public void SearchLyric_Instrumental_ShouldReturnSuccess(string artist, string song)
{
// Arrange
var lyricsClient = new LyricFindProvider();
SearchRequest searchRequest = CreateSearchRequest(testData);
var searchRequest = new ArtistAndSongSearchRequest(artist, song);

// Act
var searchResult = lyricsClient.SearchLyric(searchRequest);
Expand All @@ -54,6 +54,26 @@ public void SearchLyric_IntegrationDynamicData_Instrumental(LyricsTestData testD
Assert.True(searchResult.Instrumental);
}

[RegionalTestTheory(includeRegions: new[] { "AM", "RU" })]
[InlineData("rush", "Tom Sawyer")]
public void SearchLyric_LyricAreNotAvailableInRegion_ShouldRegionRestrictedStatus(string artist, string song)
{
// Arrange
var lyricsClient = new LyricFindProvider();
var searchRequest = new ArtistAndSongSearchRequest(artist, song);

// Act
var searchResult = lyricsClient.SearchLyric(searchRequest);

// Assert
Assert.NotNull(searchResult);
Assert.True(searchResult.IsEmpty());
Assert.Equal(ResponseStatusCode.RegionRestricted, searchResult.ResponseStatusCode);
Assert.Equal(ExternalProviderType.LyricFind, searchResult.ExternalProviderType);
Assert.True(string.IsNullOrEmpty(searchResult.ResponseMessage));
Assert.False(searchResult.Instrumental);
}

[Theory]
[InlineData("asdfasdfasdfasdf", "asdfasdfasdfasdf")]
public void SearchLyric_NotExistsLyrics_ShouldReturnNoDataFoundStatus(string artist, string song)
Expand Down Expand Up @@ -99,13 +119,13 @@ public async Task SearchLyricAsync_IntegrationDynamicData_Success(LyricsTestData
Assert.False(searchResult.Instrumental);
}

[Theory]
[MemberData(nameof(GetTestData), parameters: "Providers\\LyricFind\\instrumental_test_data.json")]
public async Task SearchLyricAsync_IntegrationDynamicData_Instrumental(LyricsTestData testData)
[RegionalTestTheory(excludeRegions: new[] { "AM", "RU" })]
[InlineData("rush", "yyz")]
public async Task SearchLyricAsync_Instrumental_ShouldReturnSuccess(string artist, string song)
{
// Arrange
var lyricsClient = new LyricFindProvider();
SearchRequest searchRequest = CreateSearchRequest(testData);
var searchRequest = new ArtistAndSongSearchRequest(artist, song);

// Act
var searchResult = await lyricsClient.SearchLyricAsync(searchRequest);
Expand All @@ -119,6 +139,26 @@ public async Task SearchLyricAsync_IntegrationDynamicData_Instrumental(LyricsTes
Assert.True(searchResult.Instrumental);
}

[RegionalTestTheory(includeRegions: new[] { "AM", "RU" })]
[InlineData("rush", "Tom Sawyer")]
public async Task SearchLyricAsync_LyricAreNotAvailableInRegion_ShouldRegionRestrictedStatus(string artist, string song)
{
// Arrange
var lyricsClient = new LyricFindProvider();
var searchRequest = new ArtistAndSongSearchRequest(artist, song);

// Act
var searchResult = await lyricsClient.SearchLyricAsync(searchRequest);

// Assert
Assert.NotNull(searchResult);
Assert.True(searchResult.IsEmpty());
Assert.Equal(ResponseStatusCode.RegionRestricted, searchResult.ResponseStatusCode);
Assert.Equal(ExternalProviderType.LyricFind, searchResult.ExternalProviderType);
Assert.True(string.IsNullOrEmpty(searchResult.ResponseMessage));
Assert.False(searchResult.Instrumental);
}

[Theory]
[InlineData("asdfasdfasdfasdf", "asdfasdfasdfasdf")]
public async Task SearchLyricAsync_NotExistsLyrics_ShouldReturnNoDataFoundStatus(string artist, string song)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ public void SearchLyric_IntegrationDynamicData_Success(LyricsTestData testData)
}

[Theory]
[MemberData(nameof(GetTestData), parameters: "Providers\\SongLyrics\\instrumental_test_data.json")]
public void SearchLyric_IntegrationDynamicData_Instrumental(LyricsTestData testData)
[InlineData("rush", "yyz")]
public void SearchLyric_Instrumental_ShouldReturnSuccess(string artist, string song)
{
// Arrange
var lyricsClient = new SongLyricsProvider();
SearchRequest searchRequest = CreateSearchRequest(testData);
var searchRequest = new ArtistAndSongSearchRequest(artist, song);

// Act
var searchResult = lyricsClient.SearchLyric(searchRequest);
Expand Down Expand Up @@ -100,12 +100,12 @@ public async Task SearchLyricAsync_IntegrationDynamicData_Success(LyricsTestData
}

[Theory]
[MemberData(nameof(GetTestData), parameters: "Providers\\SongLyrics\\instrumental_test_data.json")]
public async Task SearchLyricAsync_IntegrationDynamicData_Instrumental(LyricsTestData testData)
[InlineData("rush", "yyz")]
public async Task SearchLyricAsync_Instrumental_ShouldReturnSuccess(string artist, string song)
{
// Arrange
var lyricsClient = new SongLyricsProvider();
SearchRequest searchRequest = CreateSearchRequest(testData);
var searchRequest = new ArtistAndSongSearchRequest(artist, song);

// Act
var searchResult = await lyricsClient.SearchLyricAsync(searchRequest);
Expand Down

This file was deleted.

Loading

0 comments on commit 8d94e60

Please sign in to comment.