# TenantIsolationContract<TStore\>

> Tracon documentation. Tracon is a .NET package family that adds a control
> plane on Microsoft Agent Framework, published as NuGet packages that run
> inside your own ASP.NET Core application.
>
> Page: https://tracon.dev/api/tracon.testing.contracts.storage.tenantisolationcontract-1/
> Summary: TenantIsolationContract<TStore\>: The shared base that checks a store's tenant isolation both ways.

<div class="api-provenance">Namespace Tracon.Testing.Contracts.Storage · Assembly <code>Tracon.Testing.Contracts.Xunit.dll</code></div>

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

```csharp
public abstract class TenantIsolationContract<TStore> : StoreCancellationContract<TStore>, IAsyncLifetime, IAsyncDisposable
```

<dl class="api-relations"><dt>Type Parameters</dt><dd><code>TStore</code> The store type under test.</dd><dt>Inheritance</dt><dd><a href="https://learn.microsoft.com/dotnet/api/system.object">object</a> ← <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/">StoreCancellationContract&lt;TStore&gt;</a> ← <a href="/api/tracon.testing.contracts.storage.tenantisolationcontract-1/">TenantIsolationContract&lt;TStore&gt;</a></dd><dt>Implements</dt><dd>IAsyncLifetime, <a href="https://learn.microsoft.com/dotnet/api/system.iasyncdisposable">IAsyncDisposable</a></dd><dt>Inherited Members</dt><dd><a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_CreateStoreAsync">StoreCancellationContract&lt;TStore&gt;.CreateStoreAsync()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_InitializeAsync">StoreCancellationContract&lt;TStore&gt;.InitializeAsync()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_DisposeAsync">StoreCancellationContract&lt;TStore&gt;.DisposeAsync()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_OnDisposeAsync">StoreCancellationContract&lt;TStore&gt;.OnDisposeAsync()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_CancellableReadAsync_System_Threading_CancellationToken_">StoreCancellationContract&lt;TStore&gt;.CancellableReadAsync(CancellationToken)</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_CancellableWriteAsync_System_Threading_CancellationToken_">StoreCancellationContract&lt;TStore&gt;.CancellableWriteAsync(CancellationToken)</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_WroteAnythingAsync">StoreCancellationContract&lt;TStore&gt;.WroteAnythingAsync()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_Canceled_token_throws_on_read">StoreCancellationContract&lt;TStore&gt;.Canceled_token_throws_on_read()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_The_write_this_contract_checks_can_be_read_back">StoreCancellationContract&lt;TStore&gt;.The_write_this_contract_checks_can_be_read_back()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_Canceled_token_throws_on_write_and_leaves_no_trace">StoreCancellationContract&lt;TStore&gt;.Canceled_token_throws_on_write_and_leaves_no_trace()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_Store">StoreCancellationContract&lt;TStore&gt;.Store</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.gettype">object.GetType()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone">object.MemberwiseClone()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.tostring">object.ToString()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object)">object.Equals(object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object)">object.Equals(object?, object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.referenceequals">object.ReferenceEquals(object?, object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.gethashcode">object.GetHashCode()</a></dd></dl>

## Remarks

Derived contracts write only their own scenarios and the four isolation
hooks; the lifecycle plumbing and the cancellation promise come from
[Storage.StoreCancellationContract](/api/tracon.testing.contracts.storage/).

**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

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1__ctor"></a> TenantIsolationContract\(\)

```csharp
protected TenantIsolationContract()
```

## Fields

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_TenantA"></a> TenantA

The tenant that writes the data.

```csharp
protected const string TenantA = "tenant-a"
```

#### Field Value

 [string](https://learn.microsoft.com/dotnet/api/system.string)

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_TenantB"></a> TenantB

The tenant that must not see the data.

```csharp
protected const string TenantB = "tenant-b"
```

#### Field Value

 [string](https://learn.microsoft.com/dotnet/api/system.string)

## Properties

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_AmbientTenant"></a> AmbientTenant

The tenant stores read from [ITenantContext](/api/tracon.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.

```csharp
protected MutableTenantContext AmbientTenant { get; }
```

#### Property Value

 [MutableTenantContext](/api/tracon.testing.contracts.storage.mutabletenantcontext/)

## Methods

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_CancellableReadAsync_System_Threading_CancellationToken_"></a> CancellableReadAsync\(CancellationToken\)

Calls one **read** method of the store with the given
token.

```csharp
protected override ValueTask CancellableReadAsync(CancellationToken cancellationToken)
```

#### Parameters

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The token the store must observe.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask)

The completion task; any result is discarded.

#### 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](/api/tracon.testing.contracts.storage/) therefore makes no store call at
all on an empty store -- overrides this with its own read.

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_CancellableWriteAsync_System_Threading_CancellationToken_"></a> CancellableWriteAsync\(CancellationToken\)

Calls one **write** method of the store with the given
token.

```csharp
protected override ValueTask CancellableWriteAsync(CancellationToken cancellationToken)
```

#### Parameters

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The token the store must observe.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask)

The completion task.

#### Remarks

The write must be one [StoreCancellationContract.WroteAnythingAsync](/api/tracon.testing.contracts.storage/) 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](/api/tracon.testing.contracts.storage/)
runs the same pair with a live token and requires the read to find it.

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_CountAsync_System_String_System_Threading_CancellationToken_"></a> CountAsync\(string, CancellationToken\)

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

```csharp
protected abstract ValueTask<int> CountAsync(string tenantId, CancellationToken cancellationToken)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The reading tenant.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The token every store call inside the hook must be handed; see
[TenantIsolationContract.SeedAsync](/api/tracon.testing.contracts.storage/).

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[int](https://learn.microsoft.com/dotnet/api/system.int32)\>

The number of records seen.

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_ExistsAsync_System_String_System_Object_"></a> ExistsAsync\(string, object\)

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

```csharp
protected abstract ValueTask<bool> ExistsAsync(string tenantId, object key)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The reading tenant.

`key` [object](https://learn.microsoft.com/dotnet/api/system.object)

The key returned by [TenantIsolationContract.SeedAsync](/api/tracon.testing.contracts.storage/).

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[bool](https://learn.microsoft.com/dotnet/api/system.boolean)\>

[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if the record is visible.

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_Same_name_lives_independently_across_two_tenants"></a> Same\_name\_lives\_independently\_across\_two\_tenants\(\)

```csharp
[Fact]
public Task Same_name_lives_independently_across_two_tenants()
```

#### Returns

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_SeedAsync_System_String_System_String_System_Threading_CancellationToken_"></a> SeedAsync\(string, string, CancellationToken\)

Writes a sample record for the given tenant.

```csharp
protected abstract ValueTask<object> SeedAsync(string tenantId, string name, CancellationToken cancellationToken)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The tenant that owns the record.

`name` [string](https://learn.microsoft.com/dotnet/api/system.string)

The name distinguishing the record. The same name may be used by two tenants.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.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

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[object](https://learn.microsoft.com/dotnet/api/system.object)\>

The key to use for reading the record back.

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_Tenant_cannot_delete_another_tenants_record"></a> Tenant\_cannot\_delete\_another\_tenants\_record\(\)

```csharp
[Fact]
public Task Tenant_cannot_delete_another_tenants_record()
```

#### Returns

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_Tenant_cannot_read_another_tenants_record"></a> Tenant\_cannot\_read\_another\_tenants\_record\(\)

```csharp
[Fact]
public Task Tenant_cannot_read_another_tenants_record()
```

#### Returns

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_Tenant_does_not_see_another_tenants_record_in_the_list"></a> Tenant\_does\_not\_see\_another\_tenants\_record\_in\_the\_list\(\)

```csharp
[Fact]
public Task Tenant_does_not_see_another_tenants_record_in_the_list()
```

#### Returns

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_Tenant_reads_its_own_record"></a> Tenant\_reads\_its\_own\_record\(\)

```csharp
[Fact]
public Task Tenant_reads_its_own_record()
```

#### Returns

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_TryDeleteAsync_System_String_System_Object_"></a> TryDeleteAsync\(string, object\)

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

```csharp
protected virtual ValueTask<bool?> TryDeleteAsync(string tenantId, object key)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The tenant attempting the delete.

`key` [object](https://learn.microsoft.com/dotnet/api/system.object)

The key returned by [TenantIsolationContract.SeedAsync](/api/tracon.testing.contracts.storage/).

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[bool](https://learn.microsoft.com/dotnet/api/system.boolean)?\>

[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if the delete succeeded, [false](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool)
if the record was not found; [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) if the store does
not offer deletion.

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_TryOverwriteAsync_System_String_System_String_"></a> TryOverwriteAsync\(string, string\)

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

```csharp
protected virtual ValueTask<bool> TryOverwriteAsync(string tenantId, string name)
```

#### Parameters

`tenantId` [string](https://learn.microsoft.com/dotnet/api/system.string)

The tenant attempting the update.

`name` [string](https://learn.microsoft.com/dotnet/api/system.string)

The record's name.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[bool](https://learn.microsoft.com/dotnet/api/system.boolean)\>

[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if the update was attempted; [false](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool)
if the store does not support this scenario.

#### Remarks

The default implementation calls [TenantIsolationContract.SeedAsync](/api/tracon.testing.contracts.storage/) a second
time: writing a record with the same name verifies that two tenants
can carry the same name independently.

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_UseAmbientTenant_Tracon_Testing_Contracts_Storage_MutableTenantContext_"></a> UseAmbientTenant\(MutableTenantContext\)

Attaches to a tenant context shared per class (used by multiple
tests) and resets the tenant back to [TenantIsolationContract.TenantA](/api/tracon.testing.contracts.storage/).

```csharp
protected void UseAmbientTenant(MutableTenantContext tenant)
```

#### Parameters

`tenant` [MutableTenantContext](/api/tracon.testing.contracts.storage.mutabletenantcontext/)

The shared context supplied by the schema/test CLASS fixture.

#### Remarks

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

### <a id="Tracon_Testing_Contracts_Storage_TenantIsolationContract_1_WroteAnythingAsync"></a> WroteAnythingAsync\(\)

Reports whether [StoreCancellationContract.CancellableWriteAsync](/api/tracon.testing.contracts.storage/) left anything
behind.

```csharp
protected override ValueTask<bool> WroteAnythingAsync()
```

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[bool](https://learn.microsoft.com/dotnet/api/system.boolean)\>

[true](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/bool) if the store holds any record the write would have created.
