Skip to content
Tracon

JobStoreContract

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

Behavior tests for the IJobStore contract.

public abstract class JobStoreContract : TenantIsolationContract<IJobStore>, IAsyncLifetime, IAsyncDisposable

objectTenantIsolationContract<IJobStore>JobStoreContract

IAsyncLifetime, IAsyncDisposable

TenantIsolationContract<IJobStore>.TenantA, TenantIsolationContract<IJobStore>.TenantB, TenantIsolationContract<IJobStore>.CreateStoreAsync(), TenantIsolationContract<IJobStore>.UseAmbientTenant(MutableTenantContext), TenantIsolationContract<IJobStore>.InitializeAsync(), TenantIsolationContract<IJobStore>.DisposeAsync(), TenantIsolationContract<IJobStore>.OnDisposeAsync(), TenantIsolationContract<IJobStore>.SeedAsync(string, string), TenantIsolationContract<IJobStore>.ExistsAsync(string, object), TenantIsolationContract<IJobStore>.CountAsync(string), TenantIsolationContract<IJobStore>.TryDeleteAsync(string, object), TenantIsolationContract<IJobStore>.TryOverwriteAsync(string, string), TenantIsolationContract<IJobStore>.Tenant_cannot_read_another_tenants_record(), TenantIsolationContract<IJobStore>.Tenant_reads_its_own_record(), TenantIsolationContract<IJobStore>.Tenant_does_not_see_another_tenants_record_in_the_list(), TenantIsolationContract<IJobStore>.Tenant_cannot_delete_another_tenants_record(), TenantIsolationContract<IJobStore>.Same_name_lives_independently_across_two_tenants(), TenantIsolationContract<IJobStore>.AmbientTenant, TenantIsolationContract<IJobStore>.Store, object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

The in-memory store and the PostgreSQL store must pass the same scenarios. The lease-expiry test runs against real time (a short lease + a short wait): the PostgreSQL implementation does not take its clock through an injectable TimeProvider, so fake time cannot be used here.

protected JobStoreContract()

CancelAsync_does_not_affect_another_tenants_job()

Section titled “ CancelAsync_does_not_affect_another_tenants_job()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 196)]
public Task CancelAsync_does_not_affect_another_tenants_job()

Task

CancelAsync_works_only_in_eligible_states(JobStatus, bool)

Section titled “ CancelAsync_works_only_in_eligible_states(JobStatus, bool)”
[Theory("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 180)]
[InlineData(new object?[] { JobStatus.Pending, true })]
[InlineData(new object?[] { JobStatus.Completed, false })]
[InlineData(new object?[] { JobStatus.Failed, false })]
public Task CancelAsync_works_only_in_eligible_states(JobStatus status, bool expected)

status JobStatus

expected bool

Task

[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 144)]
public Task CompleteAsync_releases_the_lease()

Task

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

protected override ValueTask<int> CountAsync(string tenantId)

tenantId string

The reading tenant.

ValueTask<int>

The number of records seen.

Enqueued_job_defaults_to_the_default_lane()

Section titled “ Enqueued_job_defaults_to_the_default_lane()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 262)]
public Task Enqueued_job_defaults_to_the_default_lane()

Task

Enqueued_job_starts_pending_and_creates_items()

Section titled “ Enqueued_job_starts_pending_and_creates_items()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 36)]
public Task Enqueued_job_starts_pending_and_creates_items()

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.

GetAsync_returns_null_for_another_tenant()

Section titled “ GetAsync_returns_null_for_another_tenant()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 207)]
public Task GetAsync_returns_null_for_another_tenant()

Task

Job_can_be_re_leased_once_the_lease_expires()

Section titled “ Job_can_be_re_leased_once_the_lease_expires()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 84)]
public Task Job_can_be_re_leased_once_the_lease_expires()

Task

[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 52)]
public Task Job_not_yet_due_cannot_be_leased()

Task

Lease_with_a_lane_filter_never_returns_a_job_from_an_unlisted_lane()

Section titled “ Lease_with_a_lane_filter_never_returns_a_job_from_an_unlisted_lane()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 283)]
public Task Lease_with_a_lane_filter_never_returns_a_job_from_an_unlisted_lane()

Task

Lease_with_a_lane_filter_only_returns_a_job_in_that_lane()

Section titled “ Lease_with_a_lane_filter_only_returns_a_job_in_that_lane()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 270)]
public Task Lease_with_a_lane_filter_only_returns_a_job_in_that_lane()

Task

Lease_with_an_empty_lanes_list_applies_no_filter()

Section titled “ Lease_with_an_empty_lanes_list_applies_no_filter()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 299)]
public Task Lease_with_an_empty_lanes_list_applies_no_filter()

Task

Lease_with_null_lanes_applies_no_filter()

Section titled “ Lease_with_null_lanes_applies_no_filter()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 291)]
public Task Lease_with_null_lanes_applies_no_filter()

Task

Leased_job_is_not_given_to_another_worker_before_expiry()

Section titled “ Leased_job_is_not_given_to_another_worker_before_expiry()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 75)]
public Task Leased_job_is_not_given_to_another_worker_before_expiry()

Task

Leasing_marks_Leased_and_increments_the_attempt()

Section titled “ Leasing_marks_Leased_and_increments_the_attempt()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 60)]
public Task Leasing_marks_Leased_and_increments_the_attempt()

Task

MarkRunningAsync_works_only_for_the_real_owner()

Section titled “ MarkRunningAsync_works_only_for_the_real_owner()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 99)]
public Task MarkRunningAsync_works_only_for_the_real_owner()

Task

[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 227)]
public Task QueryAsync_filters_by_handler_key()

Task

[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 331)]
public Task QueryAsync_filters_by_lane()

Task

[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 215)]
public Task QueryAsync_filters_by_tenant()

Task

QueryAsync_lane_filter_does_not_leak_another_tenants_job()

Section titled “ QueryAsync_lane_filter_does_not_leak_another_tenants_job()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 319)]
public Task QueryAsync_lane_filter_does_not_leak_another_tenants_job()

Task

QueryAsync_returns_nothing_for_a_handler_key_no_job_uses()

Section titled “ QueryAsync_returns_nothing_for_a_handler_key_no_job_uses()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 242)]
public Task QueryAsync_returns_nothing_for_a_handler_key_no_job_uses()

Task

QueryAsync_treats_an_empty_handler_key_as_a_filter_that_matches_nothing()

Section titled “ QueryAsync_treats_an_empty_handler_key_as_a_filter_that_matches_nothing()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 250)]
public Task QueryAsync_treats_an_empty_handler_key_as_a_filter_that_matches_nothing()

Task

Queue_depth_counts_a_job_released_for_retry_as_pending_again()

Section titled “ Queue_depth_counts_a_job_released_for_retry_as_pending_again()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 434)]
public Task Queue_depth_counts_a_job_released_for_retry_as_pending_again()

Task

Queue_depth_counts_every_tenants_jobs_together()

Section titled “ Queue_depth_counts_every_tenants_jobs_together()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 405)]
public Task Queue_depth_counts_every_tenants_jobs_together()

Task

Queue_depth_drops_a_lane_once_its_last_job_finishes()

Section titled “ Queue_depth_drops_a_lane_once_its_last_job_finishes()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 418)]
public Task Queue_depth_drops_a_lane_once_its_last_job_finishes()

Task

Queue_depth_groups_pending_jobs_by_lane()

Section titled “ Queue_depth_groups_pending_jobs_by_lane()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 347)]
public Task Queue_depth_groups_pending_jobs_by_lane()

Task

Queue_depth_is_empty_when_no_job_is_open()

Section titled “ Queue_depth_is_empty_when_no_job_is_open()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 343)]
public Task Queue_depth_is_empty_when_no_job_is_open()

Task

Queue_depth_never_counts_a_terminal_status()

Section titled “ Queue_depth_never_counts_a_terminal_status()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 382)]
public Task Queue_depth_never_counts_a_terminal_status()

Task

Queue_depth_separates_the_open_statuses()

Section titled “ Queue_depth_separates_the_open_statuses()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 360)]
public Task Queue_depth_separates_the_open_statuses()

Task

ReleaseForRetryAsync_returns_to_pending_without_resetting_the_attempt()

Section titled “ ReleaseForRetryAsync_returns_to_pending_without_resetting_the_attempt()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 164)]
public Task ReleaseForRetryAsync_returns_to_pending_without_resetting_the_attempt()

Task

ReportItemAsync_counts_successful_and_failed_items_separately()

Section titled “ ReportItemAsync_counts_successful_and_failed_items_separately()”
[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 127)]
public Task ReportItemAsync_counts_successful_and_failed_items_separately()

Task

[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 113)]
public Task ReportItemAsync_is_idempotent()

Task

[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/JobStoreContract.cs", 307)]
public Task Retry_preserves_the_jobs_lane()

Task

Writes a sample record for the given tenant.

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

tenantId string

The tenant that owns the record.

name string

The name distinguishing the record. The same name may be used by two tenants.

ValueTask<object>

The key to use for reading the record back.

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.

There is no delete in the job queue; the only destructive operation available to a tenant is cancel.