Added full-text search to voice works search provider. Added initial tests for voice works full-text search.
This commit is contained in:
@@ -20,8 +20,9 @@ public sealed class VoiceWorkSearchConfiguration : IEntityTypeConfiguration<Voic
|
|||||||
|
|
||||||
// MariaDB/MySQL (Pomelo) fulltext (BOOLEAN/NATURAL) — create an FT index
|
// MariaDB/MySQL (Pomelo) fulltext (BOOLEAN/NATURAL) — create an FT index
|
||||||
// Pomelo supports .HasMethod("FULLTEXT"). If your version doesn't, add it in a migration SQL.
|
// Pomelo supports .HasMethod("FULLTEXT"). If your version doesn't, add it in a migration SQL.
|
||||||
//builder.HasIndex(x => x.SearchText)
|
builder.HasIndex(x => x.SearchText)
|
||||||
// .HasDatabaseName("FT_SearchText")
|
.IsFullText()
|
||||||
// .HasMethod("FULLTEXT");
|
.HasDatabaseName("FT_SearchText");
|
||||||
|
//.HasMethod("FULLTEXT");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
||||||
|
|
||||||
|
public interface IVoiceWorkFullTextSearch
|
||||||
|
{
|
||||||
|
IQueryable<int> MatchingIds(AppDbContext context, string searchText);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace JSMR.Infrastructure.Data.Repositories.VoiceWorks;
|
||||||
|
|
||||||
|
public class MySqlVoiceWorkFullTextSearch : IVoiceWorkFullTextSearch
|
||||||
|
{
|
||||||
|
public IQueryable<int> MatchingIds(AppDbContext context, string searchText) =>
|
||||||
|
context.VoiceWorkSearches
|
||||||
|
.Where(v => EF.Functions.Match(v.SearchText, searchText, MySqlMatchSearchMode.Boolean) > 0)
|
||||||
|
.Select(v => v.VoiceWorkId);
|
||||||
|
}
|
||||||
@@ -18,15 +18,15 @@ public class VoiceWorkQuery
|
|||||||
//public VoiceWorkSearch? VoiceWorkSearch { get; init; }
|
//public VoiceWorkSearch? VoiceWorkSearch { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<VoiceWorkSearchResult, VoiceWorkSearchCriteria, VoiceWorkSortField, VoiceWorkQuery>, IVoiceWorkSearchProvider
|
public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSearch fullTextSearch) : SearchProvider<VoiceWorkSearchResult, VoiceWorkSearchCriteria, VoiceWorkSortField, VoiceWorkQuery>, IVoiceWorkSearchProvider
|
||||||
{
|
{
|
||||||
protected override IQueryable<VoiceWorkQuery> GetBaseQuery()
|
protected override IQueryable<VoiceWorkQuery> GetBaseQuery()
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
from voiceWork in context.VoiceWorks
|
from voiceWork in context.VoiceWorks.AsNoTracking()
|
||||||
join englishVoiceWork in context.EnglishVoiceWorks on voiceWork.VoiceWorkId equals englishVoiceWork.VoiceWorkId into ps
|
join englishVoiceWork in context.EnglishVoiceWorks.AsNoTracking() on voiceWork.VoiceWorkId equals englishVoiceWork.VoiceWorkId into ps
|
||||||
from englishVoiceWork in ps.DefaultIfEmpty()
|
from englishVoiceWork in ps.DefaultIfEmpty()
|
||||||
join circle in context.Circles on voiceWork.CircleId equals circle.CircleId into cs
|
join circle in context.Circles.AsNoTracking() on voiceWork.CircleId equals circle.CircleId into cs
|
||||||
from circle in cs.DefaultIfEmpty()
|
from circle in cs.DefaultIfEmpty()
|
||||||
//join voiceWorkLocalization in context.VoiceWorkLocalizations on voiceWork.VoiceWorkId equals voiceWorkLocalization.VoiceWorkId into vwl
|
//join voiceWorkLocalization in context.VoiceWorkLocalizations on voiceWork.VoiceWorkId equals voiceWorkLocalization.VoiceWorkId into vwl
|
||||||
//from voiceWorkLocalization in vwl.DefaultIfEmpty()
|
//from voiceWorkLocalization in vwl.DefaultIfEmpty()
|
||||||
@@ -44,10 +44,12 @@ public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<Voic
|
|||||||
{
|
{
|
||||||
IQueryable<VoiceWorkQuery> filteredQuery = query;
|
IQueryable<VoiceWorkQuery> filteredQuery = query;
|
||||||
|
|
||||||
// TODO: Full Text Search implementation
|
filteredQuery = ApplyKeywordsFilter(filteredQuery, criteria);
|
||||||
//filteredQuery = FuzzyKeywordSearch(filteredQuery, searchProperties.Keywords);
|
filteredQuery = ApplyCircleStatusFilter(filteredQuery, criteria);
|
||||||
//filteredQuery = FuzzyTitleSearch(filteredQuery, searchProperties.Title);
|
filteredQuery = ApplyTagStatusFilter(filteredQuery, criteria);
|
||||||
//filteredQuery = FuzzyCircleSearch(filteredQuery, searchProperties.Circle);
|
//filteredQuery = FilterCreatorStatus(filteredQuery, searchProperties.CreatorStatus, _voiceWorkContext);
|
||||||
|
filteredQuery = ApplyTagIdsFilter(filteredQuery, criteria);
|
||||||
|
filteredQuery = ApplyCreatorIdsFilter(filteredQuery, criteria);
|
||||||
|
|
||||||
switch (criteria.SaleStatus)
|
switch (criteria.SaleStatus)
|
||||||
{
|
{
|
||||||
@@ -86,25 +88,43 @@ public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<Voic
|
|||||||
if (criteria.MaxDownloads is not null)
|
if (criteria.MaxDownloads is not null)
|
||||||
filteredQuery = filteredQuery.Where(x => x.VoiceWork.Downloads <= criteria.MaxDownloads.Value);
|
filteredQuery = filteredQuery.Where(x => x.VoiceWork.Downloads <= criteria.MaxDownloads.Value);
|
||||||
|
|
||||||
|
return filteredQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IQueryable<VoiceWorkQuery> ApplyKeywordsFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(criteria.Keywords))
|
||||||
|
return query;
|
||||||
|
|
||||||
|
var voiceWorkIds = fullTextSearch.MatchingIds(context, criteria.Keywords);
|
||||||
|
|
||||||
|
return query.Where(x => voiceWorkIds.Contains(x.VoiceWork.VoiceWorkId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private IQueryable<VoiceWorkQuery> ApplyCircleStatusFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
|
||||||
|
{
|
||||||
|
if (criteria.CircleStatus is null)
|
||||||
|
return query;
|
||||||
|
|
||||||
switch (criteria.CircleStatus)
|
switch (criteria.CircleStatus)
|
||||||
{
|
{
|
||||||
case CircleStatus.NotBlacklisted:
|
case CircleStatus.NotBlacklisted:
|
||||||
filteredQuery = filteredQuery.Where(x => x.Circle.Blacklisted == false);
|
query = query.Where(q =>
|
||||||
break;
|
!context.Circles.Any(c => c.CircleId == q.VoiceWork.CircleId && c.Blacklisted));
|
||||||
case CircleStatus.Favorited:
|
|
||||||
filteredQuery = filteredQuery.Where(x => x.Circle.Favorite);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CircleStatus.Blacklisted:
|
case CircleStatus.Blacklisted:
|
||||||
filteredQuery = filteredQuery.Where(x => x.Circle.Blacklisted);
|
query = query.Where(q =>
|
||||||
|
context.Circles.Any(c => c.CircleId == q.VoiceWork.CircleId && c.Blacklisted));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CircleStatus.Favorited:
|
||||||
|
query = query.Where(q =>
|
||||||
|
context.Circles.Any(c => c.CircleId == q.VoiceWork.CircleId && c.Favorite));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredQuery = ApplyTagStatusFilter(filteredQuery, criteria);
|
return query;
|
||||||
//filteredQuery = FilterCreatorStatus(filteredQuery, searchProperties.CreatorStatus, _voiceWorkContext);
|
|
||||||
filteredQuery = FilterTagIds(filteredQuery, criteria);
|
|
||||||
filteredQuery = FilterCreatorIds(filteredQuery, criteria);
|
|
||||||
|
|
||||||
return filteredQuery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<VoiceWorkQuery> ApplyTagStatusFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
|
private IQueryable<VoiceWorkQuery> ApplyTagStatusFilter(IQueryable<VoiceWorkQuery> query, VoiceWorkSearchCriteria criteria)
|
||||||
@@ -155,7 +175,7 @@ public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<Voic
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<VoiceWorkQuery> FilterTagIds(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
|
private IQueryable<VoiceWorkQuery> ApplyTagIdsFilter(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
|
||||||
{
|
{
|
||||||
if (criteria.TagIds.Length == 0)
|
if (criteria.TagIds.Length == 0)
|
||||||
return filteredQuery;
|
return filteredQuery;
|
||||||
@@ -195,7 +215,7 @@ public class VoiceWorkSearchProvider(AppDbContext context) : SearchProvider<Voic
|
|||||||
return filteredQuery;
|
return filteredQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<VoiceWorkQuery> FilterCreatorIds(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
|
private IQueryable<VoiceWorkQuery> ApplyCreatorIdsFilter(IQueryable<VoiceWorkQuery> filteredQuery, VoiceWorkSearchCriteria criteria)
|
||||||
{
|
{
|
||||||
if (criteria.CreatorIds.Length == 0)
|
if (criteria.CreatorIds.Length == 0)
|
||||||
return filteredQuery;
|
return filteredQuery;
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.8" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.8" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.8" />
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class MariaDbFixture : IAsyncLifetime
|
|||||||
|
|
||||||
await using AppDbContext context = CreateDbContext();
|
await using AppDbContext context = CreateDbContext();
|
||||||
await context.Database.EnsureCreatedAsync();
|
await context.Database.EnsureCreatedAsync();
|
||||||
|
//await context.Database.MigrateAsync(); // Testing
|
||||||
await OnInitializedAsync(context);
|
await OnInitializedAsync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,43 @@ public class VoiceWorkSearchProviderFixture : MariaDbFixture
|
|||||||
new() { EnglishTagId = 8, TagId = 8, Name = "Maid" }
|
new() { EnglishTagId = 8, TagId = 8, Name = "Maid" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
context.VoiceWorkTags.AddRange(
|
||||||
|
new() { VoiceWorkId = 1, TagId = 1 }, // ASMR
|
||||||
|
new() { VoiceWorkId = 1, TagId = 2 }, // Office Lady
|
||||||
|
|
||||||
|
new() { VoiceWorkId = 2, TagId = 1 }, // ASMR
|
||||||
|
new() { VoiceWorkId = 2, TagId = 3 }, // Heartwarming
|
||||||
|
new() { VoiceWorkId = 2, TagId = 4 }, // Elf / Fairy
|
||||||
|
new() { VoiceWorkId = 2, TagId = 5 }, // Tsundere
|
||||||
|
new() { VoiceWorkId = 2, TagId = 6 }, // All Happy
|
||||||
|
new() { VoiceWorkId = 2, TagId = 7 }, // Gal
|
||||||
|
new() { VoiceWorkId = 2, TagId = 8 } // Maid
|
||||||
|
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 3, TagId = 1 },
|
||||||
|
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 4, TagId = 1 },
|
||||||
|
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 },
|
||||||
|
//new() { VoiceWorkId = 5, TagId = 1 }
|
||||||
|
);
|
||||||
|
|
||||||
context.Creators.AddRange(
|
context.Creators.AddRange(
|
||||||
new() { CreatorId = 1, Name = "陽向葵ゅか", Favorite = true },
|
new() { CreatorId = 1, Name = "陽向葵ゅか", Favorite = true },
|
||||||
new() { CreatorId = 2, Name = "秋野かえで" },
|
new() { CreatorId = 2, Name = "秋野かえで" },
|
||||||
@@ -60,6 +97,15 @@ public class VoiceWorkSearchProviderFixture : MariaDbFixture
|
|||||||
new() { CreatorId = 5, Name = "山田じぇみ子", Blacklisted = true }
|
new() { CreatorId = 5, Name = "山田じぇみ子", Blacklisted = true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// <Product Id> <Maker Id> <Circle Name> <Product Name> <Product Description> <Tags> <Creators>
|
||||||
|
context.VoiceWorkSearches.AddRange(
|
||||||
|
new() { VoiceWorkId = 1, SearchText = "RJ0000001 RG00001 Good Dreams Today Sounds An average product. ASMR Office Lady" },
|
||||||
|
new() { VoiceWorkId = 2, SearchText = "RJ0000002 RG00002 Sweet Dreams Super Comfy ASMR An amazing product! ASMR Heartwarming Elf / Fairy Tsundere All Happy Gal Maid" },
|
||||||
|
new() { VoiceWorkId = 3, SearchText = "RJ0000003 RG00003 Nightmare Fuel Low Effort A bad product." },
|
||||||
|
new() { VoiceWorkId = 4, SearchText = "RJ0000004 RG00001 Good Dreams Tomorrow Sounds A average upcoming product." },
|
||||||
|
new() { VoiceWorkId = 5, SearchText = "RJ0000005 RG00002 Sweet Dreams Super Comfy ASMR+ All your favorite sounds, plus more!" }
|
||||||
|
);
|
||||||
|
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,11 +9,19 @@ namespace JSMR.Tests.Integration;
|
|||||||
|
|
||||||
public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture) : IClassFixture<VoiceWorkSearchProviderFixture>
|
public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture) : IClassFixture<VoiceWorkSearchProviderFixture>
|
||||||
{
|
{
|
||||||
|
private VoiceWorkSearchProvider InitializeVoiceWorkSearchProvider(AppDbContext context)
|
||||||
|
{
|
||||||
|
MySqlVoiceWorkFullTextSearch fullTextSearch = new();
|
||||||
|
VoiceWorkSearchProvider provider = new(context, fullTextSearch);
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Filter_Default()
|
public async Task Filter_Default()
|
||||||
{
|
{
|
||||||
await using AppDbContext context = fixture.CreateDbContext();
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
VoiceWorkSearchProvider provider = new(context);
|
VoiceWorkSearchProvider provider = InitializeVoiceWorkSearchProvider(context);
|
||||||
|
|
||||||
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
{
|
{
|
||||||
@@ -21,11 +29,7 @@ public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture
|
|||||||
{
|
{
|
||||||
SaleStatus = SaleStatus.Available,
|
SaleStatus = SaleStatus.Available,
|
||||||
CircleStatus = CircleStatus.NotBlacklisted
|
CircleStatus = CircleStatus.NotBlacklisted
|
||||||
},
|
}
|
||||||
SortOptions =
|
|
||||||
[
|
|
||||||
new(VoiceWorkSortField.ReleaseDate, Application.Common.Search.SortDirection.Descending)
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await provider.SearchAsync(options);
|
var result = await provider.SearchAsync(options);
|
||||||
@@ -40,7 +44,7 @@ public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture
|
|||||||
public async Task Filter_Upcoming_Favorite()
|
public async Task Filter_Upcoming_Favorite()
|
||||||
{
|
{
|
||||||
await using AppDbContext context = fixture.CreateDbContext();
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
VoiceWorkSearchProvider provider = new(context);
|
VoiceWorkSearchProvider provider = InitializeVoiceWorkSearchProvider(context);
|
||||||
|
|
||||||
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
{
|
{
|
||||||
@@ -48,11 +52,7 @@ public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture
|
|||||||
{
|
{
|
||||||
SaleStatus = SaleStatus.Upcoming,
|
SaleStatus = SaleStatus.Upcoming,
|
||||||
CircleStatus = CircleStatus.Favorited
|
CircleStatus = CircleStatus.Favorited
|
||||||
},
|
}
|
||||||
SortOptions =
|
|
||||||
[
|
|
||||||
new(VoiceWorkSortField.ReleaseDate, Application.Common.Search.SortDirection.Descending)
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await provider.SearchAsync(options);
|
var result = await provider.SearchAsync(options);
|
||||||
@@ -67,7 +67,7 @@ public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture
|
|||||||
public async Task Filter_Availble_Blacklisted()
|
public async Task Filter_Availble_Blacklisted()
|
||||||
{
|
{
|
||||||
await using AppDbContext context = fixture.CreateDbContext();
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
VoiceWorkSearchProvider provider = new(context);
|
VoiceWorkSearchProvider provider = InitializeVoiceWorkSearchProvider(context);
|
||||||
|
|
||||||
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
{
|
{
|
||||||
@@ -75,11 +75,7 @@ public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture
|
|||||||
{
|
{
|
||||||
SaleStatus = SaleStatus.Available,
|
SaleStatus = SaleStatus.Available,
|
||||||
CircleStatus = CircleStatus.Blacklisted
|
CircleStatus = CircleStatus.Blacklisted
|
||||||
},
|
}
|
||||||
SortOptions =
|
|
||||||
[
|
|
||||||
new(VoiceWorkSortField.ReleaseDate, Application.Common.Search.SortDirection.Descending)
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await provider.SearchAsync(options);
|
var result = await provider.SearchAsync(options);
|
||||||
@@ -89,4 +85,93 @@ public class VoiceWorkSearchProviderTests(VoiceWorkSearchProviderFixture fixture
|
|||||||
result.Items.ShouldAllBe(item => item.SalesDate != null);
|
result.Items.ShouldAllBe(item => item.SalesDate != null);
|
||||||
result.Items.ShouldNotContain(item => item.ExpectedDate != null);
|
result.Items.ShouldNotContain(item => item.ExpectedDate != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_Keywords_Basic()
|
||||||
|
{
|
||||||
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
|
VoiceWorkSearchProvider provider = InitializeVoiceWorkSearchProvider(context);
|
||||||
|
|
||||||
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
|
{
|
||||||
|
Criteria = new()
|
||||||
|
{
|
||||||
|
Keywords = "ASMR"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await provider.SearchAsync(options);
|
||||||
|
|
||||||
|
result.Items.Length.ShouldBe(3);
|
||||||
|
result.TotalItems.ShouldBe(3);
|
||||||
|
result.Items.ShouldAllBe(item => item.Tags.Any(tag => tag.Name == "ASMR") || item.ProductName.Contains("ASMR") || (item.Description ?? string.Empty).Contains("ASMR"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_Keywords_Not_Good()
|
||||||
|
{
|
||||||
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
|
VoiceWorkSearchProvider provider = InitializeVoiceWorkSearchProvider(context);
|
||||||
|
|
||||||
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
|
{
|
||||||
|
Criteria = new()
|
||||||
|
{
|
||||||
|
Keywords = "ASMR -Good"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await provider.SearchAsync(options);
|
||||||
|
|
||||||
|
result.Items.Length.ShouldBe(2);
|
||||||
|
result.TotalItems.ShouldBe(2);
|
||||||
|
result.Items.ShouldAllBe(item => item.Tags.Any(tag => tag.Name == "ASMR") || item.ProductName.Contains("ASMR") || (item.Description ?? string.Empty).Contains("ASMR"));
|
||||||
|
result.Items.ShouldAllBe(item => !item.Tags.Any(tag => tag.Name == "Good") || !item.ProductName.Contains("Good") || !(item.Description ?? string.Empty).Contains("Good"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_Keywords_Dreams_And_Amazing_Or_Favorite()
|
||||||
|
{
|
||||||
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
|
VoiceWorkSearchProvider provider = InitializeVoiceWorkSearchProvider(context);
|
||||||
|
|
||||||
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
|
{
|
||||||
|
Criteria = new()
|
||||||
|
{
|
||||||
|
Keywords = "Dreams + (Amazing|Favorite)"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await provider.SearchAsync(options);
|
||||||
|
|
||||||
|
result.Items.Length.ShouldBe(2);
|
||||||
|
result.TotalItems.ShouldBe(2);
|
||||||
|
|
||||||
|
result.Items
|
||||||
|
.OrderBy(item => item.ProductId)
|
||||||
|
.Select(item => item.ProductId)
|
||||||
|
.ShouldBe(["RJ0000002", "RJ0000005"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_Keywords_Phrase_Search()
|
||||||
|
{
|
||||||
|
await using AppDbContext context = fixture.CreateDbContext();
|
||||||
|
VoiceWorkSearchProvider provider = InitializeVoiceWorkSearchProvider(context);
|
||||||
|
|
||||||
|
var options = new SearchOptions<VoiceWorkSearchCriteria, VoiceWorkSortField>()
|
||||||
|
{
|
||||||
|
Criteria = new()
|
||||||
|
{
|
||||||
|
Keywords = "\"All Your Favorite\""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await provider.SearchAsync(options);
|
||||||
|
|
||||||
|
result.Items.Length.ShouldBe(1);
|
||||||
|
result.TotalItems.ShouldBe(1);
|
||||||
|
result.Items.ShouldAllBe(item => (item.Description ?? string.Empty).Contains("All Your Favorite", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user