Skip to content
Tracon

WorkflowRequestStoreContract

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

Behavior tests for the IWorkflowRequestStore contract.

public abstract class WorkflowRequestStoreContract : TenantIsolationContract<IWorkflowRequestStore>, IAsyncLifetime, IAsyncDisposable
Inheritance
object ← StoreCancellationContract<IWorkflowRequestStore> ← TenantIsolationContract<IWorkflowRequestStore> ← WorkflowRequestStoreContract
Implements
IAsyncLifetime, IAsyncDisposable
Inherited Members
TenantIsolationContract<IWorkflowRequestStore>.TenantA, TenantIsolationContract<IWorkflowRequestStore>.TenantB, TenantIsolationContract<IWorkflowRequestStore>.UseAmbientTenant(MutableTenantContext), TenantIsolationContract<IWorkflowRequestStore>.SeedAsync(string, string, CancellationToken), TenantIsolationContract<IWorkflowRequestStore>.ExistsAsync(string, object), TenantIsolationContract<IWorkflowRequestStore>.CountAsync(string, CancellationToken), TenantIsolationContract<IWorkflowRequestStore>.TryDeleteAsync(string, object), TenantIsolationContract<IWorkflowRequestStore>.TryOverwriteAsync(string, string), TenantIsolationContract<IWorkflowRequestStore>.CancellableReadAsync(CancellationToken), TenantIsolationContract<IWorkflowRequestStore>.CancellableWriteAsync(CancellationToken), TenantIsolationContract<IWorkflowRequestStore>.WroteAnythingAsync(), TenantIsolationContract<IWorkflowRequestStore>.Tenant_cannot_read_another_tenants_record(), TenantIsolationContract<IWorkflowRequestStore>.Tenant_reads_its_own_record(), TenantIsolationContract<IWorkflowRequestStore>.Tenant_does_not_see_another_tenants_record_in_the_list(), TenantIsolationContract<IWorkflowRequestStore>.Tenant_cannot_delete_another_tenants_record(), TenantIsolationContract<IWorkflowRequestStore>.Same_name_lives_independently_across_two_tenants(), TenantIsolationContract<IWorkflowRequestStore>.AmbientTenant, StoreCancellationContract<IWorkflowRequestStore>.CreateStoreAsync(), StoreCancellationContract<IWorkflowRequestStore>.InitializeAsync(), StoreCancellationContract<IWorkflowRequestStore>.DisposeAsync(), StoreCancellationContract<IWorkflowRequestStore>.OnDisposeAsync(), StoreCancellationContract<IWorkflowRequestStore>.CancellableReadAsync(CancellationToken), StoreCancellationContract<IWorkflowRequestStore>.CancellableWriteAsync(CancellationToken), StoreCancellationContract<IWorkflowRequestStore>.WroteAnythingAsync(), StoreCancellationContract<IWorkflowRequestStore>.Canceled_token_throws_on_read(), StoreCancellationContract<IWorkflowRequestStore>.The_write_this_contract_checks_can_be_read_back(), StoreCancellationContract<IWorkflowRequestStore>.Canceled_token_throws_on_write_and_leaves_no_trace(), StoreCancellationContract<IWorkflowRequestStore>.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 two decisions a unit test of the runner cannot prove: which row is THE open row of a logical request (the same request id in the same session held by several runs), and which of an answer and an expiry reaches a row first. The first is a listing question and the second is a race, so both are cases here.

The Storage.TenantIsolationContract hooks are wired to IWorkflowRequestStore.AddAsync and IWorkflowRequestStore.ListAsync; the store offers no delete, so another tenant’s chance to change a row is IWorkflowRequestStore.MarkAnsweredAsync, which has a case of its own.

protected WorkflowRequestStoreContract()

A_closed_row_leaves_the_list_and_cannot_be_marked_answered()

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

Task

A_logical_request_stays_listed_while_an_older_run_of_it_closes()

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

Task

[Fact]
public Task A_page_outside_the_range_is_cut_to_it()

Task

A_row_expires_at_its_time_and_not_before()

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

Task

[Fact]
public Task A_row_is_closed_once()

Task

A_row_without_a_time_limit_reads_back_without_one()

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

Task

A_run_lists_its_requests_in_event_order()

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

Task

A_run_that_is_closed_or_has_an_open_row_is_not_stranded()

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

Task

A_run_that_still_waits_after_its_last_row_closed_is_listed_as_stranded()

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

Task

A_second_answer_names_its_own_run_and_keeps_the_first_time()

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

Task

A_written_row_is_read_back_with_every_field()

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

Task

Another_tenant_cannot_mark_a_row_answered()

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

Task

Concurrent_expiries_close_each_row_exactly_once()

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

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.

[Fact]
public Task Erasing_the_run_removes_its_rows()

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.

Expiry_closes_only_the_open_rows_whose_time_has_come_and_returns_them()

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

Task

Expiry_closes_the_oldest_deadlines_first_and_no_more_than_max()

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

Task

Expiry_closes_the_rows_of_every_tenant_and_keeps_the_answer_mark()

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

Task

Marking_answered_marks_every_open_row_of_the_logical_request_and_no_other()

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

Task

Opens the run row an inbox row 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_requests.run_id is a foreign key into runs; the in-memory implementation has no such link, and the hook does nothing.

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.

SetRunStatusAsync(Guid, string, RunStatus)

Section titled “ SetRunStatusAsync(Guid, string, RunStatus)”

Gives a run the status a run has when it stops waiting, or starts to.

protected virtual ValueTask<bool> SetRunStatusAsync(Guid runId, string tenantId, RunStatus status)

runId Guid

The run, opened by WorkflowRequestStoreContract.PrepareRunAsync.

tenantId string

The run’s tenant.

status RunStatus

The status to give.

ValueTask<bool>

true when the run now has the status; false when the store under test has no run table, and the cases that need a waiting run do not apply.

Stranded_runs_are_listed_oldest_closure_first_and_no_more_than_max()

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

Task

The_list_can_be_limited_to_one_workflow()

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

Task

The_list_is_oldest_first_and_pages_without_gaps_or_repeats()

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

Task

The_same_request_id_in_two_sessions_is_two_requests()

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

Task

The_same_request_of_a_session_is_listed_once_under_its_newest_open_run()

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

Task

The_state_filter_runs_before_the_page_so_a_page_is_never_short()

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

Task

Two_tenants_hold_the_same_session_and_request_without_seeing_each_other()

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

Task

Writing_the_same_run_and_request_twice_keeps_the_first_row()

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

Task