.NET API
TenantIsolationContract<TStore\>
Tracon.Testing.Contracts.Xunit.dllThe shared base that checks a store’s tenant isolation both ways.
public abstract class TenantIsolationContract<TStore> : StoreCancellationContract<TStore>, IAsyncLifetime, IAsyncDisposable- Type Parameters
TStoreThe store type under test.- Inheritance
- object ← StoreCancellationContract<TStore> ← TenantIsolationContract<TStore>
- Implements
- IAsyncLifetime, IAsyncDisposable
- Inherited Members
- StoreCancellationContract<TStore>.CreateStoreAsync(), StoreCancellationContract<TStore>.InitializeAsync(), StoreCancellationContract<TStore>.DisposeAsync(), StoreCancellationContract<TStore>.OnDisposeAsync(), StoreCancellationContract<TStore>.CancellableReadAsync(CancellationToken), StoreCancellationContract<TStore>.CancellableWriteAsync(CancellationToken), StoreCancellationContract<TStore>.WroteAnythingAsync(), StoreCancellationContract<TStore>.Canceled_token_throws_on_read(), StoreCancellationContract<TStore>.The_write_this_contract_checks_can_be_read_back(), StoreCancellationContract<TStore>.Canceled_token_throws_on_write_and_leaves_no_trace(), StoreCancellationContract<TStore>.Store, object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()
Remarks
Section titled “Remarks”Derived contracts write only their own scenarios and the four isolation hooks; the lifecycle plumbing and the cancellation promise come from Storage.StoreCancellationContract.
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.
Constructors
Section titled “Constructors”TenantIsolationContract()
Section titled “ TenantIsolationContract()”protected TenantIsolationContract()Fields
Section titled “Fields”TenantA
Section titled “ TenantA”The tenant that writes the data.
protected const string TenantA = "tenant-a"Field Value
Section titled “Field Value”TenantB
Section titled “ TenantB”The tenant that must not see the data.
protected const string TenantB = "tenant-b"Field Value
Section titled “Field Value”Properties
Section titled “Properties”AmbientTenant
Section titled “ AmbientTenant”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; }Property Value
Section titled “Property Value”Methods
Section titled “Methods”CancellableReadAsync(CancellationToken)
Section titled “ CancellableReadAsync(CancellationToken)”Calls one read method of the store with the given token.
protected override ValueTask CancellableReadAsync(CancellationToken cancellationToken)Parameters
Section titled “Parameters”cancellationToken CancellationToken
The token the store must observe.
Returns
Section titled “Returns”The completion task; any result is discarded.
Remarks
Section titled “Remarks”The listing hook is the read: it needs no key, so it can be called on an untouched store. A contract whose store has no listing endpoint – and whose TenantIsolationContract.CountAsync therefore makes no store call at all on an empty store – overrides this with its own read.
CancellableWriteAsync(CancellationToken)
Section titled “ CancellableWriteAsync(CancellationToken)”Calls one write method of the store with the given token.
protected override ValueTask CancellableWriteAsync(CancellationToken cancellationToken)Parameters
Section titled “Parameters”cancellationToken CancellationToken
The token the store must observe.
Returns
Section titled “Returns”The completion task.
Remarks
Section titled “Remarks”The write must be one StoreCancellationContract.WroteAnythingAsync can see. A write this contract cannot read back proves nothing about side effects, which is why StoreCancellationContract.The_write_this_contract_checks_can_be_read_back runs the same pair with a live token and requires the read to find it.
CountAsync(string, CancellationToken)
Section titled “ CountAsync(string, CancellationToken)”Reports how many records the given tenant sees through the listing endpoint.
protected abstract ValueTask<int> CountAsync(string tenantId, CancellationToken cancellationToken)Parameters
Section titled “Parameters”tenantId string
The reading tenant.
cancellationToken CancellationToken
The token every store call inside the hook must be handed; see TenantIsolationContract.SeedAsync.
Returns
Section titled “Returns”The number of records seen.
ExistsAsync(string, object)
Section titled “ ExistsAsync(string, object)”Reports whether the record is visible from the given tenant’s viewpoint.
protected abstract ValueTask<bool> ExistsAsync(string tenantId, object key)Parameters
Section titled “Parameters”tenantId string
The reading tenant.
key object
The key returned by TenantIsolationContract.SeedAsync.
Returns
Section titled “Returns”true if the record is visible.
Same_name_lives_independently_across_two_tenants()
Section titled “ Same_name_lives_independently_across_two_tenants()”[Fact]public Task Same_name_lives_independently_across_two_tenants()Returns
Section titled “Returns”SeedAsync(string, string, CancellationToken)
Section titled “ SeedAsync(string, string, CancellationToken)”Writes a sample record for the given tenant.
protected abstract ValueTask<object> SeedAsync(string tenantId, string name, CancellationToken cancellationToken)Parameters
Section titled “Parameters”tenantId string
The tenant that owns the record.
name string
The name distinguishing the record. The same name may be used by two tenants.
cancellationToken CancellationToken
The token every store call inside the hook must be handed. The cancellation contract calls this hook with a token that is already cancelled, so a hook that drops the token reports a store as sound when it is not.
Returns
Section titled “Returns”The key to use for reading the record back.
Tenant_cannot_delete_another_tenants_record()
Section titled “ Tenant_cannot_delete_another_tenants_record()”[Fact]public Task Tenant_cannot_delete_another_tenants_record()Returns
Section titled “Returns”Tenant_cannot_read_another_tenants_record()
Section titled “ Tenant_cannot_read_another_tenants_record()”[Fact]public Task Tenant_cannot_read_another_tenants_record()Returns
Section titled “Returns”Tenant_does_not_see_another_tenants_record_in_the_list()
Section titled “ Tenant_does_not_see_another_tenants_record_in_the_list()”[Fact]public Task Tenant_does_not_see_another_tenants_record_in_the_list()Returns
Section titled “Returns”Tenant_reads_its_own_record()
Section titled “ Tenant_reads_its_own_record()”[Fact]public Task Tenant_reads_its_own_record()Returns
Section titled “Returns”TryDeleteAsync(string, object)
Section titled “ TryDeleteAsync(string, object)”Attempts to delete the record on behalf of the given tenant.
protected virtual ValueTask<bool?> TryDeleteAsync(string tenantId, object key)Parameters
Section titled “Parameters”tenantId string
The tenant attempting the delete.
key object
The key returned by TenantIsolationContract.SeedAsync.
Returns
Section titled “Returns”true if the delete succeeded, false if the record was not found; null if the store does not offer deletion.
TryOverwriteAsync(string, string)
Section titled “ TryOverwriteAsync(string, string)”Attempts to change the record’s content on behalf of the given tenant.
protected virtual ValueTask<bool> TryOverwriteAsync(string tenantId, string name)Parameters
Section titled “Parameters”tenantId string
The tenant attempting the update.
name string
The record’s name.
Returns
Section titled “Returns”true if the update was attempted; false if the store does not support this scenario.
Remarks
Section titled “Remarks”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.
UseAmbientTenant(MutableTenantContext)
Section titled “ UseAmbientTenant(MutableTenantContext)”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)Parameters
Section titled “Parameters”tenant MutableTenantContext
The shared context supplied by the schema/test CLASS fixture.
Remarks
Section titled “Remarks”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 StoreCancellationContract.CreateStoreAsync to attach to that object and reset any state a previous test may have left on TenantIsolationContract.TenantB.
WroteAnythingAsync()
Section titled “ WroteAnythingAsync()”Reports whether StoreCancellationContract.CancellableWriteAsync left anything behind.
protected override ValueTask<bool> WroteAnythingAsync()Returns
Section titled “Returns”true if the store holds any record the write would have created.