Skip to content

Commit 79b5c3e

Browse files
authored
Merge pull request #22 from linkdotnet/feature/skill-table
Feature/skill table
2 parents c48eea9 + ff5ad82 commit 79b5c3e

File tree

65 files changed

+1263
-514
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1263
-514
lines changed

LinkDotNet.Blog.IntegrationTests/Infrastructure/Persistence/Sql/BlogPostRepositoryTests.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace LinkDotNet.Blog.IntegrationTests.Infrastructure.Persistence.Sql
1010
{
11-
public sealed class BlogPostRepositoryTests : SqlDatabaseTestBase
11+
public sealed class BlogPostRepositoryTests : SqlDatabaseTestBase<BlogPost>
1212
{
1313
[Fact]
1414
public async Task ShouldLoadBlogPost()
@@ -17,7 +17,7 @@ public async Task ShouldLoadBlogPost()
1717
await DbContext.BlogPosts.AddAsync(blogPost);
1818
await DbContext.SaveChangesAsync();
1919

20-
var blogPostFromRepo = await BlogPostRepository.GetByIdAsync(blogPost.Id);
20+
var blogPostFromRepo = await Repository.GetByIdAsync(blogPost.Id, post => post.Tags);
2121

2222
blogPostFromRepo.Should().NotBeNull();
2323
blogPostFromRepo.Title.Should().Be("Title");
@@ -35,7 +35,7 @@ public async Task ShouldSaveBlogPost()
3535
{
3636
var blogPost = BlogPost.Create("Title", "Subtitle", "Content", "url", true, tags: new[] { "Tag 1", "Tag 2" });
3737

38-
await BlogPostRepository.StoreAsync(blogPost);
38+
await Repository.StoreAsync(blogPost);
3939

4040
var blogPostFromContext = await DbContext.BlogPosts.Include(b => b.Tags).AsNoTracking().SingleOrDefaultAsync(s => s.Id == blogPost.Id);
4141
blogPostFromContext.Should().NotBeNull();
@@ -56,7 +56,7 @@ public async Task ShouldGetAllBlogPosts()
5656
await DbContext.BlogPosts.AddAsync(blogPost);
5757
await DbContext.SaveChangesAsync();
5858

59-
var blogPostsFromRepo = (await BlogPostRepository.GetAllAsync()).ToList();
59+
var blogPostsFromRepo = (await Repository.GetAllAsync(include: post => post.Tags)).ToList();
6060

6161
blogPostsFromRepo.Should().NotBeNull();
6262
blogPostsFromRepo.Should().HaveCount(1);
@@ -77,11 +77,11 @@ public async Task ShouldBeUpdateable()
7777
var blogPost = new BlogPostBuilder().Build();
7878
await DbContext.BlogPosts.AddAsync(blogPost);
7979
await DbContext.SaveChangesAsync();
80-
var blogPostFromDb = await BlogPostRepository.GetByIdAsync(blogPost.Id);
80+
var blogPostFromDb = await Repository.GetByIdAsync(blogPost.Id);
8181
var updater = new BlogPostBuilder().WithTitle("New Title").Build();
8282
blogPostFromDb.Update(updater);
8383

84-
await BlogPostRepository.StoreAsync(blogPostFromDb);
84+
await Repository.StoreAsync(blogPostFromDb);
8585

8686
var blogPostAfterSave = await DbContext.BlogPosts.AsNoTracking().SingleAsync(b => b.Id == blogPostFromDb.Id);
8787
blogPostAfterSave.Title.Should().Be("New Title");
@@ -93,13 +93,14 @@ public async Task ShouldFilterAndOrder()
9393
var olderPost = new BlogPostBuilder().Build();
9494
var newerPost = new BlogPostBuilder().Build();
9595
var filteredOutPost = new BlogPostBuilder().WithTitle("FilterOut").Build();
96-
await BlogPostRepository.StoreAsync(olderPost);
97-
await BlogPostRepository.StoreAsync(newerPost);
98-
await BlogPostRepository.StoreAsync(filteredOutPost);
96+
await Repository.StoreAsync(olderPost);
97+
await Repository.StoreAsync(newerPost);
98+
await Repository.StoreAsync(filteredOutPost);
9999

100-
var blogPosts = await BlogPostRepository.GetAllAsync(
100+
var blogPosts = await Repository.GetAllAsync(
101101
bp => bp.Title != "FilterOut",
102102
bp => bp.UpdatedDate,
103+
null,
103104
false);
104105

105106
var retrievedPosts = blogPosts.ToList();
@@ -112,9 +113,9 @@ public async Task ShouldFilterAndOrder()
112113
public async Task ShouldDelete()
113114
{
114115
var blogPost = new BlogPostBuilder().Build();
115-
await BlogPostRepository.StoreAsync(blogPost);
116+
await Repository.StoreAsync(blogPost);
116117

117-
await BlogPostRepository.DeleteAsync(blogPost.Id);
118+
await Repository.DeleteAsync(blogPost.Id);
118119

119120
(await DbContext.BlogPosts.AsNoTracking().AnyAsync(b => b.Id == blogPost.Id)).Should().BeFalse();
120121
}
Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
using System.Threading.Tasks;
22
using FluentAssertions;
33
using LinkDotNet.Blog.TestUtilities;
4+
using LinkDotNet.Domain;
45
using Xunit;
56

67
namespace LinkDotNet.Blog.IntegrationTests.Infrastructure.Persistence.Sql
78
{
8-
public class ProfileRepositoryTests : SqlDatabaseTestBase
9+
public class ProfileRepositoryTests : SqlDatabaseTestBase<ProfileInformationEntry>
910
{
1011
[Fact]
1112
public async Task ShouldSaveAndRetrieveAllEntries()
1213
{
1314
var item1 = new ProfileInformationEntryBuilder().WithContent("key1").Build();
1415
var item2 = new ProfileInformationEntryBuilder().WithContent("key2").Build();
15-
await ProfileRepository.StoreAsync(item1);
16-
await ProfileRepository.StoreAsync(item2);
16+
await Repository.StoreAsync(item1);
17+
await Repository.StoreAsync(item2);
1718

18-
var items = await ProfileRepository.GetAllAsync();
19+
var items = await Repository.GetAllAsync();
1920

2021
items[0].Content.Should().Be("key1");
2122
items[1].Content.Should().Be("key2");
@@ -26,12 +27,12 @@ public async Task ShouldDelete()
2627
{
2728
var item1 = new ProfileInformationEntryBuilder().WithContent("key1").Build();
2829
var item2 = new ProfileInformationEntryBuilder().WithContent("key2").Build();
29-
await ProfileRepository.StoreAsync(item1);
30-
await ProfileRepository.StoreAsync(item2);
30+
await Repository.StoreAsync(item1);
31+
await Repository.StoreAsync(item2);
3132

32-
await ProfileRepository.DeleteAsync(item1.Id);
33+
await Repository.DeleteAsync(item1.Id);
3334

34-
var items = await ProfileRepository.GetAllAsync();
35+
var items = await Repository.GetAllAsync();
3536
items.Should().HaveCount(1);
3637
items[0].Id.Should().Be(item2.Id);
3738
}
@@ -40,11 +41,11 @@ public async Task ShouldDelete()
4041
public async Task NoopOnDeleteWhenEntryNotFound()
4142
{
4243
var item = new ProfileInformationEntryBuilder().WithContent("key1").Build();
43-
await ProfileRepository.StoreAsync(item);
44+
await Repository.StoreAsync(item);
4445

45-
await ProfileRepository.DeleteAsync("SomeIdWhichHopefullyDoesNotExist");
46+
await Repository.DeleteAsync("SomeIdWhichHopefullyDoesNotExist");
4647

47-
(await ProfileRepository.GetAllAsync()).Should().HaveCount(1);
48+
(await Repository.GetAllAsync()).Should().HaveCount(1);
4849
}
4950
}
5051
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.Threading.Tasks;
2+
using FluentAssertions;
3+
using LinkDotNet.Blog.TestUtilities;
4+
using LinkDotNet.Domain;
5+
using Xunit;
6+
7+
namespace LinkDotNet.Blog.IntegrationTests.Infrastructure.Persistence.Sql
8+
{
9+
public class SkillRepositoryTests : SqlDatabaseTestBase<Skill>
10+
{
11+
[Fact]
12+
public async Task ShouldSaveAndRetrieveAllEntries()
13+
{
14+
var skill = new SkillBuilder()
15+
.WithSkillName("C#")
16+
.WithIconUrl("url")
17+
.WithCapability("Backend")
18+
.WithProficiencyLevel(ProficiencyLevel.Expert).Build();
19+
await Repository.StoreAsync(skill);
20+
21+
var items = await Repository.GetAllAsync();
22+
23+
items.Should().HaveCount(1);
24+
items[0].Name.Should().Be("C#");
25+
items[0].IconUrl.Should().Be("url");
26+
items[0].Capability.Should().Be("Backend");
27+
items[0].ProficiencyLevel.Should().Be(ProficiencyLevel.Expert);
28+
}
29+
30+
[Fact]
31+
public async Task ShouldDelete()
32+
{
33+
var skill1 = new SkillBuilder().Build();
34+
var skill2 = new SkillBuilder().Build();
35+
await Repository.StoreAsync(skill1);
36+
await Repository.StoreAsync(skill2);
37+
38+
await Repository.DeleteAsync(skill1.Id);
39+
40+
var items = await Repository.GetAllAsync();
41+
items.Should().HaveCount(1);
42+
items[0].Id.Should().Be(skill2.Id);
43+
}
44+
45+
[Fact]
46+
public async Task NoopOnDeleteWhenEntryNotFound()
47+
{
48+
var item = new SkillBuilder().Build();
49+
await Repository.StoreAsync(item);
50+
51+
await Repository.DeleteAsync("SomeIdWhichHopefullyDoesNotExist");
52+
53+
(await Repository.GetAllAsync()).Should().HaveCount(1);
54+
}
55+
}
56+
}

LinkDotNet.Blog.IntegrationTests/SqlDatabaseTestBase.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
using System;
22
using System.Data.Common;
33
using System.Threading.Tasks;
4+
using LinkDotNet.Domain;
45
using LinkDotNet.Infrastructure.Persistence.Sql;
56
using Microsoft.Data.Sqlite;
67
using Microsoft.EntityFrameworkCore;
78
using Xunit;
89

910
namespace LinkDotNet.Blog.IntegrationTests
1011
{
11-
public abstract class SqlDatabaseTestBase : IAsyncLifetime, IAsyncDisposable
12+
public abstract class SqlDatabaseTestBase<TEntity> : IAsyncLifetime, IAsyncDisposable
13+
where TEntity : Entity
1214
{
1315
protected SqlDatabaseTestBase()
1416
{
1517
var options = new DbContextOptionsBuilder()
1618
.UseSqlite(CreateInMemoryConnection())
1719
.Options;
1820
DbContext = new BlogDbContext(options);
19-
BlogPostRepository = new BlogPostRepository(new BlogDbContext(options));
20-
ProfileRepository = new ProfileRepository(new BlogDbContext(options));
21+
Repository = new Repository<TEntity>(new BlogDbContext(options));
2122
}
2223

23-
protected BlogPostRepository BlogPostRepository { get; }
24-
25-
protected ProfileRepository ProfileRepository { get; }
24+
protected Repository<TEntity> Repository { get; }
2625

2726
protected BlogDbContext DbContext { get; }
2827

LinkDotNet.Blog.IntegrationTests/Web/Pages/Admin/DraftBlogPostPageTests.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,25 @@
55
using LinkDotNet.Blog.TestUtilities;
66
using LinkDotNet.Blog.Web.Pages.Admin;
77
using LinkDotNet.Blog.Web.Shared;
8+
using LinkDotNet.Domain;
89
using LinkDotNet.Infrastructure.Persistence;
910
using Microsoft.Extensions.DependencyInjection;
1011
using Xunit;
1112

1213
namespace LinkDotNet.Blog.IntegrationTests.Web.Pages.Admin
1314
{
14-
public class DraftBlogPostPageTests : SqlDatabaseTestBase
15+
public class DraftBlogPostPageTests : SqlDatabaseTestBase<BlogPost>
1516
{
1617
[Fact]
1718
public async Task ShouldOnlyShowPublishedPosts()
1819
{
1920
var publishedPost = new BlogPostBuilder().WithTitle("Published").IsPublished().Build();
2021
var unpublishedPost = new BlogPostBuilder().WithTitle("Not published").IsPublished(false).Build();
21-
await BlogPostRepository.StoreAsync(publishedPost);
22-
await BlogPostRepository.StoreAsync(unpublishedPost);
22+
await Repository.StoreAsync(publishedPost);
23+
await Repository.StoreAsync(unpublishedPost);
2324
using var ctx = new TestContext();
2425
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
25-
ctx.Services.AddScoped<IBlogPostRepository>(_ => BlogPostRepository);
26+
ctx.Services.AddScoped<IRepository<BlogPost>>(_ => Repository);
2627
var cut = ctx.RenderComponent<DraftBlogPosts>();
2728
cut.WaitForState(() => cut.FindAll(".blog-card").Any());
2829

LinkDotNet.Blog.IntegrationTests/Web/Pages/BlogPostPageTests.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using LinkDotNet.Blog.TestUtilities;
88
using LinkDotNet.Blog.Web.Pages;
99
using LinkDotNet.Blog.Web.Shared;
10+
using LinkDotNet.Domain;
1011
using LinkDotNet.Infrastructure.Persistence;
1112
using Microsoft.EntityFrameworkCore;
1213
using Microsoft.Extensions.DependencyInjection;
@@ -16,13 +17,13 @@
1617

1718
namespace LinkDotNet.Blog.IntegrationTests.Web.Pages
1819
{
19-
public class BlogPostPageTests : SqlDatabaseTestBase
20+
public class BlogPostPageTests : SqlDatabaseTestBase<BlogPost>
2021
{
2122
[Fact]
2223
public async Task ShouldAddLikeOnEvent()
2324
{
2425
var publishedPost = new BlogPostBuilder().WithLikes(2).IsPublished().Build();
25-
await BlogPostRepository.StoreAsync(publishedPost);
26+
await Repository.StoreAsync(publishedPost);
2627
using var ctx = new TestContext();
2728
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
2829
RegisterComponents(ctx);
@@ -42,7 +43,7 @@ public async Task ShouldAddLikeOnEvent()
4243
public async Task ShouldSubtractLikeOnEvent()
4344
{
4445
var publishedPost = new BlogPostBuilder().WithLikes(2).IsPublished().Build();
45-
await BlogPostRepository.StoreAsync(publishedPost);
46+
await Repository.StoreAsync(publishedPost);
4647
using var ctx = new TestContext();
4748
var localStorage = new Mock<ILocalStorageService>();
4849
localStorage.Setup(l => l.ContainKeyAsync("hasLiked", default)).ReturnsAsync(true);
@@ -65,7 +66,7 @@ public async Task ShouldSubtractLikeOnEvent()
6566
public async Task ShouldSetTagsWhenAvailable()
6667
{
6768
var publishedPost = new BlogPostBuilder().IsPublished().WithTags("Tag1,Tag2").Build();
68-
await BlogPostRepository.StoreAsync(publishedPost);
69+
await Repository.StoreAsync(publishedPost);
6970
using var ctx = new TestContext();
7071
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
7172
ctx.AddTestAuthorization();
@@ -80,7 +81,7 @@ public async Task ShouldSetTagsWhenAvailable()
8081

8182
private void RegisterComponents(TestContextBase ctx, ILocalStorageService localStorageService = null)
8283
{
83-
ctx.Services.AddScoped<IBlogPostRepository>(_ => BlogPostRepository);
84+
ctx.Services.AddScoped<IRepository<BlogPost>>(_ => Repository);
8485
ctx.Services.AddScoped(_ => localStorageService ?? new Mock<ILocalStorageService>().Object);
8586
ctx.Services.AddScoped(_ => new Mock<IToastService>().Object);
8687
ctx.Services.AddScoped(_ => new Mock<IHeadElementHelper>().Object);

LinkDotNet.Blog.IntegrationTests/Web/Pages/IndexTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515

1616
namespace LinkDotNet.Blog.IntegrationTests.Web.Pages
1717
{
18-
public class IndexTests : SqlDatabaseTestBase
18+
public class IndexTests : SqlDatabaseTestBase<BlogPost>
1919
{
2020
[Fact]
2121
public async Task ShouldShowAllBlogPostsWithLatestOneFirst()
2222
{
2323
var oldestBlogPost = new BlogPostBuilder().WithTitle("Old").Build();
2424
var newestBlogPost = new BlogPostBuilder().WithTitle("New").Build();
25-
await BlogPostRepository.StoreAsync(oldestBlogPost);
26-
await BlogPostRepository.StoreAsync(newestBlogPost);
25+
await Repository.StoreAsync(oldestBlogPost);
26+
await Repository.StoreAsync(newestBlogPost);
2727
using var ctx = new TestContext();
2828
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
2929
RegisterComponents(ctx);
@@ -42,8 +42,8 @@ public async Task ShouldOnlyShowPublishedPosts()
4242
{
4343
var publishedPost = new BlogPostBuilder().WithTitle("Published").IsPublished().Build();
4444
var unpublishedPost = new BlogPostBuilder().WithTitle("Not published").IsPublished(false).Build();
45-
await BlogPostRepository.StoreAsync(publishedPost);
46-
await BlogPostRepository.StoreAsync(unpublishedPost);
45+
await Repository.StoreAsync(publishedPost);
46+
await Repository.StoreAsync(unpublishedPost);
4747
using var ctx = new TestContext();
4848
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
4949
RegisterComponents(ctx);
@@ -126,13 +126,13 @@ private async Task CreatePublishedBlogPosts(int amount)
126126
for (var i = 0; i < amount; i++)
127127
{
128128
var blogPost = new BlogPostBuilder().IsPublished().Build();
129-
await BlogPostRepository.StoreAsync(blogPost);
129+
await Repository.StoreAsync(blogPost);
130130
}
131131
}
132132

133133
private void RegisterComponents(TestContextBase ctx)
134134
{
135-
ctx.Services.AddScoped<IBlogPostRepository>(_ => BlogPostRepository);
135+
ctx.Services.AddScoped<IRepository<BlogPost>>(_ => Repository);
136136
ctx.Services.AddScoped(_ => CreateSampleAppConfiguration());
137137
ctx.Services.AddScoped(_ => new Mock<IHeadElementHelper>().Object);
138138
}

LinkDotNet.Blog.IntegrationTests/Web/Pages/SearchByTagTests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using FluentAssertions;
66
using LinkDotNet.Blog.TestUtilities;
77
using LinkDotNet.Blog.Web.Pages;
8+
using LinkDotNet.Domain;
89
using LinkDotNet.Infrastructure.Persistence;
910
using Microsoft.Extensions.DependencyInjection;
1011
using Moq;
@@ -13,7 +14,7 @@
1314

1415
namespace LinkDotNet.Blog.IntegrationTests.Web.Pages
1516
{
16-
public class SearchByTagTests : SqlDatabaseTestBase
17+
public class SearchByTagTests : SqlDatabaseTestBase<BlogPost>
1718
{
1819
[Fact]
1920
public async Task ShouldOnlyDisplayTagsGivenByParameter()
@@ -22,7 +23,7 @@ public async Task ShouldOnlyDisplayTagsGivenByParameter()
2223
await AddBlogPostWithTagAsync("Tag 1");
2324
await AddBlogPostWithTagAsync("Tag 1");
2425
await AddBlogPostWithTagAsync("Tag 2");
25-
ctx.Services.AddScoped<IBlogPostRepository>(_ => BlogPostRepository);
26+
ctx.Services.AddScoped<IRepository<BlogPost>>(_ => Repository);
2627
ctx.Services.AddScoped(_ => new Mock<IHeadElementHelper>().Object);
2728
var cut = ctx.RenderComponent<SearchByTag>(p => p.Add(s => s.Tag, "Tag 1"));
2829
cut.WaitForState(() => cut.FindAll(".blog-card").Any());
@@ -37,7 +38,7 @@ public async Task ShouldHandleSpecialCharacters()
3738
{
3839
using var ctx = new TestContext();
3940
await AddBlogPostWithTagAsync("C#");
40-
ctx.Services.AddScoped<IBlogPostRepository>(_ => BlogPostRepository);
41+
ctx.Services.AddScoped<IRepository<BlogPost>>(_ => Repository);
4142
ctx.Services.AddScoped(_ => new Mock<IHeadElementHelper>().Object);
4243
var cut = ctx.RenderComponent<SearchByTag>(p => p.Add(s => s.Tag, Uri.EscapeDataString("C#")));
4344
cut.WaitForState(() => cut.FindAll(".blog-card").Any());

0 commit comments

Comments
 (0)