# RetentionStoreContract

> 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.retentionstorecontract/
> Summary: RetentionStoreContract: Behavior and tenant isolation tests for the IRetentionStore data plane.

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

Behavior and tenant isolation tests for the [IRetentionStore](/api/tracon.iretentionstore/)
data plane.

```csharp
public abstract class RetentionStoreContract : StoreCancellationContract<IRetentionStore>, IAsyncLifetime, IAsyncDisposable
```

<dl class="api-relations"><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;IRetentionStore&gt;</a> ← <a href="/api/tracon.testing.contracts.storage.retentionstorecontract/">RetentionStoreContract</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;IRetentionStore&gt;.CreateStoreAsync()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_InitializeAsync">StoreCancellationContract&lt;IRetentionStore&gt;.InitializeAsync()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_DisposeAsync">StoreCancellationContract&lt;IRetentionStore&gt;.DisposeAsync()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_OnDisposeAsync">StoreCancellationContract&lt;IRetentionStore&gt;.OnDisposeAsync()</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_CancellableReadAsync_System_Threading_CancellationToken_">StoreCancellationContract&lt;IRetentionStore&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;IRetentionStore&gt;.CancellableWriteAsync(CancellationToken)</a>, <a href="/api/tracon.testing.contracts.storage.storecancellationcontract-1/#Tracon_Testing_Contracts_Storage_StoreCancellationContract_1_WroteAnythingAsync">StoreCancellationContract&lt;IRetentionStore&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;IRetentionStore&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;IRetentionStore&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;IRetentionStore&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;IRetentionStore&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

This contract guards against a **security
defect**: a data plane with no tenant filter, where one
tenant's retention policy could delete *all* tenants' rows.

The contract does not derive from [Storage.TenantIsolationContract](/api/tracon.testing.contracts.storage/):
the data plane has no concept of a "record" (there is no write side),
seeding goes through the target table's own store. Isolation is tested
directly here, on top of
[Storage.StoreCancellationContract](/api/tracon.testing.contracts.storage/).

## Constructors

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract__ctor"></a> RetentionStoreContract\(\)

```csharp
protected RetentionStoreContract()
```

## Fields

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_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_RetentionStoreContract_TenantB"></a> TenantB

The tenant whose data must be preserved.

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

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

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_A_failed_archive_write_deletes_nothing"></a> A\_failed\_archive\_write\_deletes\_nothing\(\)

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

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_Archive_batches_delete_only_the_rows_they_archived"></a> Archive\_batches\_delete\_only\_the\_rows\_they\_archived\(\)

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

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_Archive_read_returns_only_the_given_tenant"></a> Archive\_read\_returns\_only\_the\_given\_tenant\(\)

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

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_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.

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_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_RetentionStoreContract_Count_covers_only_the_given_tenant"></a> Count\_covers\_only\_the\_given\_tenant\(\)

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

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_Delete_does_NOT_touch_the_others_data"></a> Delete\_does\_NOT\_touch\_the\_others\_data\(\)

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

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_Row_limit_threshold_counts_only_the_given_tenant"></a> Row\_limit\_threshold\_counts\_only\_the\_given\_tenant\(\)

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

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

Writes a row for the given tenant, older than the cutoff date, that
falls under the [RetentionTargets.VoiceSessions](/api/tracon.retentiontargets/#Tracon_RetentionTargets_VoiceSessions) target.

```csharp
protected abstract ValueTask SeedOldRowAsync(string tenantId)
```

#### Parameters

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

#### Returns

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

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_SeedOldRowAsync_System_String_System_DateTimeOffset_"></a> SeedOldRowAsync\(string, DateTimeOffset\)

Writes a row like [RetentionStoreContract.SeedOldRowAsync](/api/tracon.testing.contracts.storage.retentionstorecontract/) that started at
`startedAt`.

```csharp
protected virtual ValueTask SeedOldRowAsync(string tenantId, DateTimeOffset startedAt)
```

#### Parameters

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

`startedAt` [DateTimeOffset](https://learn.microsoft.com/dotnet/api/system.datetimeoffset)

When the row started; older than the cutoff date.

#### Returns

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

#### Remarks

The default ignores `startedAt` and calls
[RetentionStoreContract.SeedOldRowAsync](/api/tracon.testing.contracts.storage.retentionstorecontract/). Override it: the archive case
writes rows whose time order is the reverse of their write order, and
that is the order that shows a delete which does not follow the archive.

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_Unknown_target_throws"></a> Unknown\_target\_throws\(\)

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

 [Task](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task)

### <a id="Tracon_Testing_Contracts_Storage_RetentionStoreContract_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.

#### Remarks

The seeded row must still be there. A sweep that threw but deleted
anyway is exactly the defect this case exists for.
