Skip to content
Tracon

TenantIsolationContract<TStore\>

Namespace Tracon.Testing.Contracts.Storage · Assembly Tracon.Testing.Contracts.Xunit.dll

The shared base that checks a store’s tenant isolation both ways.

public abstract class TenantIsolationContract<TStore> : IAsyncLifetime, IAsyncDisposable

TStore

The store type under test.

objectTenantIsolationContract<TStore>

IAsyncLifetime, IAsyncDisposable

object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

This base also carries the lifecycle plumbing shared by every store contract; derived contracts write only their own scenarios and the four isolation hooks.

The two-way check cannot be skipped. If only “B must not see it” is checked, a broken query that returns nothing at all would also pass the test. Every scenario therefore also asks “does A see its own data”, proving the filter is both sufficient and not too narrow.

protected TenantIsolationContract()

The tenant that writes the data.

protected const string TenantA = "tenant-a"

string

The tenant that must not see the data.

protected const string TenantB = "tenant-b"

string

The tenant stores read from ITenantContext. For stores that do not take the tenant as a parameter, the contract sets this property to the relevant tenant on the first line of its hooks.

protected MutableTenantContext AmbientTenant { get; }

MutableTenantContext

The store under test.

protected TStore Store { get; }

TStore

Reports how many records the given tenant sees through the listing endpoint.

protected abstract ValueTask<int> CountAsync(string tenantId)

tenantId string

The reading tenant.

ValueTask<int>

The number of records seen.

Produces an empty store for the test.

protected abstract ValueTask<TStore> CreateStoreAsync()

ValueTask<TStore>

A store ready for use.

public ValueTask DisposeAsync()

ValueTask

Reports whether the record is visible from the given tenant’s viewpoint.

protected abstract ValueTask<bool> ExistsAsync(string tenantId, object key)

tenantId string

The reading tenant.

key object

The key returned by TenantIsolationContract.SeedAsync.

ValueTask<bool>

true if the record is visible.

Called immediately after the class has been created, before it is used.

public ValueTask InitializeAsync()

ValueTask

Hook for a derived class to release its own resources.

protected virtual ValueTask OnDisposeAsync()

ValueTask

A completed task.

Same_name_lives_independently_across_two_tenants()

Section titled “ Same_name_lives_independently_across_two_tenants()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/TenantIsolationContract.cs", 201)]
public Task Same_name_lives_independently_across_two_tenants()

Task

Writes a sample record for the given tenant.

protected abstract ValueTask<object> SeedAsync(string tenantId, string name)

tenantId string

The tenant that owns the record.

name string

The name distinguishing the record. The same name may be used by two tenants.

ValueTask<object>

The key to use for reading the record back.

Tenant_cannot_delete_another_tenants_record()

Section titled “ Tenant_cannot_delete_another_tenants_record()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/TenantIsolationContract.cs", 180)]
public Task Tenant_cannot_delete_another_tenants_record()

Task

Tenant_cannot_read_another_tenants_record()

Section titled “ Tenant_cannot_read_another_tenants_record()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/TenantIsolationContract.cs", 151)]
public Task Tenant_cannot_read_another_tenants_record()

Task

Tenant_does_not_see_another_tenants_record_in_the_list()

Section titled “ Tenant_does_not_see_another_tenants_record_in_the_list()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/TenantIsolationContract.cs", 171)]
public Task Tenant_does_not_see_another_tenants_record_in_the_list()

Task

[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/TenantIsolationContract.cs", 160)]
public Task Tenant_reads_its_own_record()

Task

Attempts to delete the record on behalf of the given tenant.

protected virtual ValueTask<bool?> TryDeleteAsync(string tenantId, object key)

tenantId string

The tenant attempting the delete.

key object

The key returned by TenantIsolationContract.SeedAsync.

ValueTask<bool?>

true if the delete succeeded, false if the record was not found; null if the store does not offer deletion.

Attempts to change the record’s content on behalf of the given tenant.

protected virtual ValueTask<bool> TryOverwriteAsync(string tenantId, string name)

tenantId string

The tenant attempting the update.

name string

The record’s name.

ValueTask<bool>

true if the update was attempted; false if the store does not support this scenario.

The default implementation calls TenantIsolationContract.SeedAsync a second time: writing a record with the same name verifies that two tenants can carry the same name independently.

Attaches to a tenant context shared per class (used by multiple tests) and resets the tenant back to TenantIsolationContract.TenantA.

protected void UseAmbientTenant(MutableTenantContext tenant)

tenant MutableTenantContext

The shared context supplied by the schema/test CLASS fixture.

When store instances are set up once per class (see the schema fixtures), they all capture the SAME ITenantContext object; each test must therefore call this method inside TenantIsolationContract.CreateStoreAsync to attach to that object and reset any state a previous test may have left on TenantIsolationContract.TenantB.