Skip to content
Tracon

RetentionStoreContract

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

Behavior and tenant isolation tests for the IRetentionStore data plane.

public abstract class RetentionStoreContract : StoreCancellationContract<IRetentionStore>, IAsyncLifetime, IAsyncDisposable
Inheritance
object ← StoreCancellationContract<IRetentionStore> ← RetentionStoreContract
Implements
IAsyncLifetime, IAsyncDisposable
Inherited Members
StoreCancellationContract<IRetentionStore>.CreateStoreAsync(), StoreCancellationContract<IRetentionStore>.InitializeAsync(), StoreCancellationContract<IRetentionStore>.DisposeAsync(), StoreCancellationContract<IRetentionStore>.OnDisposeAsync(), StoreCancellationContract<IRetentionStore>.CancellableReadAsync(CancellationToken), StoreCancellationContract<IRetentionStore>.CancellableWriteAsync(CancellationToken), StoreCancellationContract<IRetentionStore>.WroteAnythingAsync(), StoreCancellationContract<IRetentionStore>.Canceled_token_throws_on_read(), StoreCancellationContract<IRetentionStore>.The_write_this_contract_checks_can_be_read_back(), StoreCancellationContract<IRetentionStore>.Canceled_token_throws_on_write_and_leaves_no_trace(), StoreCancellationContract<IRetentionStore>.Store, object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

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: 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.

protected RetentionStoreContract()

The tenant that writes the data.

protected const string TenantA = "tenant-a"

string

The tenant whose data must be preserved.

protected const string TenantB = "tenant-b"

string

[Fact]
public Task A_failed_archive_write_deletes_nothing()

Task

Archive_batches_delete_only_the_rows_they_archived()

Section titled “ Archive_batches_delete_only_the_rows_they_archived()”
[Fact]
public Task Archive_batches_delete_only_the_rows_they_archived()

Task

Archive_read_returns_only_the_given_tenant()

Section titled “ Archive_read_returns_only_the_given_tenant()”
[Fact]
public Task Archive_read_returns_only_the_given_tenant()

Task

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

protected override ValueTask CancellableReadAsync(CancellationToken cancellationToken)

cancellationToken CancellationToken

The token the store must observe.

ValueTask

The completion task; any result is discarded.

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

protected override ValueTask CancellableWriteAsync(CancellationToken cancellationToken)

cancellationToken CancellationToken

The token the store must observe.

ValueTask

The completion task.

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.

[Fact]
public Task Count_covers_only_the_given_tenant()

Task

[Fact]
public Task Delete_does_NOT_touch_the_others_data()

Task

Row_limit_threshold_counts_only_the_given_tenant()

Section titled “ Row_limit_threshold_counts_only_the_given_tenant()”
[Fact]
public Task Row_limit_threshold_counts_only_the_given_tenant()

Task

Writes a row for the given tenant, older than the cutoff date, that falls under the RetentionTargets.VoiceSessions target.

protected abstract ValueTask SeedOldRowAsync(string tenantId)

tenantId string

The tenant that owns the row.

ValueTask

The completion task.

Writes a row like RetentionStoreContract.SeedOldRowAsync that started at startedAt.

protected virtual ValueTask SeedOldRowAsync(string tenantId, DateTimeOffset startedAt)

tenantId string

The tenant that owns the row.

startedAt DateTimeOffset

When the row started; older than the cutoff date.

ValueTask

The completion task.

The default ignores startedAt and calls RetentionStoreContract.SeedOldRowAsync. 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.

[Fact]
public Task Unknown_target_throws()

Task

Reports whether StoreCancellationContract.CancellableWriteAsync left anything behind.

protected override ValueTask<bool> WroteAnythingAsync()

ValueTask<bool>

true if the store holds any record the write would have created.

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