Skip to content
Tracon

WorkflowResponseStoreContract

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

Behavior tests for the IWorkflowResponseStore contract.

public abstract class WorkflowResponseStoreContract : TenantIsolationContract<IWorkflowResponseStore>, IAsyncLifetime, IAsyncDisposable
Inheritance
object ← StoreCancellationContract<IWorkflowResponseStore> ← TenantIsolationContract<IWorkflowResponseStore> ← WorkflowResponseStoreContract
Implements
IAsyncLifetime, IAsyncDisposable
Inherited Members
TenantIsolationContract<IWorkflowResponseStore>.TenantA, TenantIsolationContract<IWorkflowResponseStore>.TenantB, TenantIsolationContract<IWorkflowResponseStore>.UseAmbientTenant(MutableTenantContext), TenantIsolationContract<IWorkflowResponseStore>.SeedAsync(string, string, CancellationToken), TenantIsolationContract<IWorkflowResponseStore>.ExistsAsync(string, object), TenantIsolationContract<IWorkflowResponseStore>.CountAsync(string, CancellationToken), TenantIsolationContract<IWorkflowResponseStore>.TryDeleteAsync(string, object), TenantIsolationContract<IWorkflowResponseStore>.TryOverwriteAsync(string, string), TenantIsolationContract<IWorkflowResponseStore>.CancellableReadAsync(CancellationToken), TenantIsolationContract<IWorkflowResponseStore>.CancellableWriteAsync(CancellationToken), TenantIsolationContract<IWorkflowResponseStore>.WroteAnythingAsync(), TenantIsolationContract<IWorkflowResponseStore>.Tenant_cannot_read_another_tenants_record(), TenantIsolationContract<IWorkflowResponseStore>.Tenant_reads_its_own_record(), TenantIsolationContract<IWorkflowResponseStore>.Tenant_does_not_see_another_tenants_record_in_the_list(), TenantIsolationContract<IWorkflowResponseStore>.Tenant_cannot_delete_another_tenants_record(), TenantIsolationContract<IWorkflowResponseStore>.Same_name_lives_independently_across_two_tenants(), TenantIsolationContract<IWorkflowResponseStore>.AmbientTenant, StoreCancellationContract<IWorkflowResponseStore>.CreateStoreAsync(), StoreCancellationContract<IWorkflowResponseStore>.InitializeAsync(), StoreCancellationContract<IWorkflowResponseStore>.DisposeAsync(), StoreCancellationContract<IWorkflowResponseStore>.OnDisposeAsync(), StoreCancellationContract<IWorkflowResponseStore>.CancellableReadAsync(CancellationToken), StoreCancellationContract<IWorkflowResponseStore>.CancellableWriteAsync(CancellationToken), StoreCancellationContract<IWorkflowResponseStore>.WroteAnythingAsync(), StoreCancellationContract<IWorkflowResponseStore>.Canceled_token_throws_on_read(), StoreCancellationContract<IWorkflowResponseStore>.The_write_this_contract_checks_can_be_read_back(), StoreCancellationContract<IWorkflowResponseStore>.Canceled_token_throws_on_write_and_leaves_no_trace(), StoreCancellationContract<IWorkflowResponseStore>.Store, object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

The store is the authority of a single-answer workflow: two concurrent answers to one request must leave exactly one claim, and two concurrent re-drives of a stopped answer must leave exactly one replacement. A unit test of the endpoint cannot prove either; only the store can, which is why the races are cases here.

The TenantIsolationContract.TryDeleteAsync hook is wired to IWorkflowResponseStore.ReplaceAsync, the one mutation of an existing record a caller asks for: another tenant must not be able to abandon a record.

protected WorkflowResponseStoreContract()

A_claim_is_found_by_its_scope_and_request()

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

Task

[Fact]
public Task A_delivered_record_can_be_replaced_too()

Task

A_recorded_answer_is_read_back_with_every_field()

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

Task

A_second_answer_to_a_claimed_request_is_not_written_and_names_the_first()

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

Task

An_abandoned_record_cannot_be_replaced_again_and_nothing_is_written()

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

Task

Another_tenant_cannot_mark_a_record_delivered()

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

Task

Answers_without_a_claim_are_all_written()

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

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.

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.

Concurrent_answers_to_one_request_leave_exactly_one_claim()

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

Task

Concurrent_replacements_of_one_record_leave_exactly_one_claim()

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

Task

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

protected override ValueTask<int> CountAsync(string tenantId, CancellationToken cancellationToken)

tenantId string

The reading tenant.

cancellationToken CancellationToken

The token every store call inside the hook must be handed; see TenantIsolationContract.SeedAsync.

ValueTask<int>

The number of records seen.

Erases a run the way a data subject erasure does.

protected virtual ValueTask<bool> EraseRunAsync(Guid runId, string tenantId)

runId Guid

The run to erase.

tenantId string

The run’s tenant.

ValueTask<bool>

true when the run was erased; false when the store under test has no run table to erase from, and the case does not apply.

Erasing_the_source_run_removes_its_answers()

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

Task

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

protected override 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.

Marking_delivered_moves_only_an_accepted_record()

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

Task

Opens the run row an answer’s source run id points at.

protected virtual ValueTask PrepareRunAsync(Guid runId, string tenantId, string sessionId)

runId Guid

The run id.

tenantId string

The tenant id.

sessionId string

The run’s execution session.

ValueTask

The completion task.

In the SQL implementations workflow_responses.source_run_id is a foreign key into runs; the in-memory implementation has no such link, and the hook does nothing.

Replacing_abandons_the_record_and_hands_the_claim_to_the_replacement()

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

Task

Replacing_an_unknown_record_writes_nothing()

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

Task

SeedAsync(string, string, CancellationToken)

Section titled “ SeedAsync(string, string, CancellationToken)”

Writes a sample record for the given tenant.

protected override ValueTask<object> SeedAsync(string tenantId, string name, CancellationToken cancellationToken)

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.

ValueTask<object>

The key to use for reading the record back.

The_answers_of_a_source_are_listed_oldest_first()

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

Task

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

protected override 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.

Two_tenants_claim_the_same_scope_and_request_without_seeing_each_other()

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

Task