Skip to content

Commit 7bfc5df

Browse files
author
Bart Koelman
committed
Added tests for multi-tenancy
1 parent 2a4d129 commit 7bfc5df

12 files changed

+1263
-1
lines changed

src/JsonApiDotNetCore/Services/JsonApiResourceService.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ public virtual async Task<TResource> CreateAsync(TResource resource, Cancellatio
201201

202202
_resourceChangeTracker.SetInitiallyStoredAttributeValues(resourceForDatabase);
203203

204-
await _resourceDefinitionAccessor.OnInitializeResourceAsync(resourceForDatabase, cancellationToken);
204+
await InitializeResourceAsync(resourceForDatabase, cancellationToken);
205205

206206
try
207207
{
@@ -243,6 +243,11 @@ public virtual async Task<TResource> CreateAsync(TResource resource, Cancellatio
243243
return resourceFromDatabase;
244244
}
245245

246+
protected virtual async Task InitializeResourceAsync(TResource resourceForDatabase, CancellationToken cancellationToken)
247+
{
248+
await _resourceDefinitionAccessor.OnInitializeResourceAsync(resourceForDatabase, cancellationToken);
249+
}
250+
246251
protected async Task AssertResourcesToAssignInRelationshipsExistAsync(TResource primaryResource, CancellationToken cancellationToken)
247252
{
248253
var missingResources = new List<MissingResourceInRelationship>();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.MultiTenancy
4+
{
5+
internal sealed class FakeTenantProvider : ITenantProvider
6+
{
7+
public Guid TenantId { get; }
8+
9+
public FakeTenantProvider(Guid tenantId)
10+
{
11+
// A real implementation would be registered at request scope and obtain the tenant ID from the request, for example
12+
// from the incoming authentication token, a custom HTTP header, the route or a query string parameter.
13+
14+
TenantId = tenantId;
15+
}
16+
}
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using JetBrains.Annotations;
3+
4+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.MultiTenancy
5+
{
6+
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
7+
public interface IHasTenant
8+
{
9+
Guid TenantId { get; set; }
10+
}
11+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.MultiTenancy
4+
{
5+
public interface ITenantProvider
6+
{
7+
Guid TenantId { get; }
8+
}
9+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using JetBrains.Annotations;
2+
using Microsoft.EntityFrameworkCore;
3+
4+
// @formatter:wrap_chained_method_calls chop_always
5+
6+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.MultiTenancy
7+
{
8+
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
9+
public sealed class MultiTenancyDbContext : DbContext
10+
{
11+
private readonly ITenantProvider _tenantProvider;
12+
13+
public DbSet<WebShop> WebShops { get; set; }
14+
public DbSet<WebProduct> WebProducts { get; set; }
15+
16+
public MultiTenancyDbContext(DbContextOptions<MultiTenancyDbContext> options, ITenantProvider tenantProvider)
17+
: base(options)
18+
{
19+
_tenantProvider = tenantProvider;
20+
}
21+
22+
protected override void OnModelCreating(ModelBuilder builder)
23+
{
24+
builder.Entity<WebShop>()
25+
.HasMany(webShop => webShop.Products)
26+
.WithOne(webProduct => webProduct.Shop)
27+
.IsRequired();
28+
29+
builder.Entity<WebShop>()
30+
.HasQueryFilter(webShop => webShop.TenantId == _tenantProvider.TenantId);
31+
32+
builder.Entity<WebProduct>()
33+
.HasQueryFilter(webProduct => webProduct.Shop.TenantId == _tenantProvider.TenantId);
34+
}
35+
}
36+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using Bogus;
3+
using TestBuildingBlocks;
4+
5+
// @formatter:wrap_chained_method_calls chop_always
6+
// @formatter:keep_existing_linebreaks true
7+
8+
namespace JsonApiDotNetCoreExampleTests.IntegrationTests.MultiTenancy
9+
{
10+
internal sealed class MultiTenancyFakers : FakerContainer
11+
{
12+
private readonly Lazy<Faker<WebShop>> _lazyWebShopFaker = new Lazy<Faker<WebShop>>(() =>
13+
new Faker<WebShop>()
14+
.UseSeed(GetFakerSeed())
15+
.RuleFor(webShop => webShop.Url, faker => faker.Internet.Url()));
16+
17+
private readonly Lazy<Faker<WebProduct>> _lazyWebProductFaker = new Lazy<Faker<WebProduct>>(() =>
18+
new Faker<WebProduct>()
19+
.UseSeed(GetFakerSeed())
20+
.RuleFor(webProduct => webProduct.Name, faker => faker.Commerce.ProductName())
21+
.RuleFor(webProduct => webProduct.Price, faker => faker.Finance.Amount()));
22+
23+
public Faker<WebShop> WebShop => _lazyWebShopFaker.Value;
24+
public Faker<WebProduct> WebProduct => _lazyWebProductFaker.Value;
25+
}
26+
}

0 commit comments

Comments
 (0)