.NET API
ModelProviderCredentialContract
Tracon.Testing.Contracts.Xunit.dllBehavior tests for a provider that honors a per-tenant credential (BYOK).
public abstract class ModelProviderCredentialContract : ModelProviderContract, IAsyncLifetime, IAsyncDisposableInheritance
Section titled “Inheritance”object ← ModelProviderContract ← ModelProviderCredentialContract
Implements
Section titled “Implements”IAsyncLifetime, IAsyncDisposable
Inherited Members
Section titled “Inherited Members”ModelProviderContract.CreateProviderAsync(), ModelProviderContract.InitializeAsync(), ModelProviderContract.DisposeAsync(), ModelProviderContract.OnDisposeAsync(), ModelProviderContract.Binding(string?, string?), ModelProviderContract.Provider_name_is_not_empty(), ModelProviderContract.Catalog_model_names_are_unique_and_not_empty(), ModelProviderContract.Create_chat_client_returns_a_client_for_the_setup_time_credential(), ModelProviderContract.Create_chat_client_returns_a_raw_client_that_builds_no_tool_call_loop(), ModelProviderContract.Create_chat_client_accepts_a_binding_whose_provider_name_differs_in_case(), ModelProviderContract.A_model_the_catalog_does_not_list_is_not_rejected(), ModelProviderContract.Concurrent_create_chat_client_calls_all_return_a_client(), ModelProviderContract.A_client_that_is_never_disposed_does_not_stop_the_provider(), ModelProviderContract.Provider, ModelProviderContract.UnknownModelName, object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()
Remarks
Section titled “Remarks”Honoring ModelProviderCredential is optional.
A provider that offers it implements
ITenantCredentialModelProvider. Deriving this class verifies
that explicit capability. A provider that does not offer BYOK does not
derive this class and records that as a ContractCoverage exemption.
Like ModelProviderContract, nothing here performs a model call: the observable consequences of a credential are which client object gets built, not what the network returns.
Constructors
Section titled “Constructors”ModelProviderCredentialContract()
Section titled “ ModelProviderCredentialContract()”protected ModelProviderCredentialContract()Properties
Section titled “Properties”Credential
Section titled “ Credential”A credential this provider can build a client from. It is never used against a real endpoint, so any syntactically valid key works.
protected abstract ModelProviderCredential Credential { get; }Property Value
Section titled “Property Value”OtherCredential
Section titled “ OtherCredential”A second credential that differs from ModelProviderCredentialContract.Credential, used to prove that two tenants do not collapse onto one client.
protected virtual ModelProviderCredential OtherCredential { get; }Property Value
Section titled “Property Value”Methods
Section titled “Methods”A_tenant_credential_is_applied_to_the_returned_client()
Section titled “ A_tenant_credential_is_applied_to_the_returned_client()”[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/Providers/ModelProviderCredentialContract.cs", 63)]public void A_tenant_credential_is_applied_to_the_returned_client()A_tenant_credential_produces_a_different_client_than_the_setup_time_one()
Section titled “ A_tenant_credential_produces_a_different_client_than_the_setup_time_one()”[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/Providers/ModelProviderCredentialContract.cs", 77)]public void A_tenant_credential_produces_a_different_client_than_the_setup_time_one()Remarks
Section titled “Remarks”The key never falls back to the setup-time key: a tenant that supplied a credential is billed on it, or the call fails. Handing back the setup-time client would silently bill the wrong tenant, and distinct instances are the observable consequence of not doing that.
An_endpoint_override_on_a_tenant_credential_is_accepted()
Section titled “ An_endpoint_override_on_a_tenant_credential_is_accepted()”[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/Providers/ModelProviderCredentialContract.cs", 132)]public void An_endpoint_override_on_a_tenant_credential_is_accepted()Remarks
Section titled “Remarks”An endpoint override is accepted; which address the resulting client uses is not observable without a network call, so this asserts acceptance only. Note that the endpoint — unlike the key — may fall back to the setup-time address when a tenant overrides only the key.
AssertCredentialIsApplied(IChatClient, ModelProviderCredential)
Section titled “ AssertCredentialIsApplied(IChatClient, ModelProviderCredential)”Verifies that a returned client will use credential
for its provider request.
protected abstract void AssertCredentialIsApplied(IChatClient client, ModelProviderCredential credential)Parameters
Section titled “Parameters”client IChatClient
The client returned for the tenant credential.
credential ModelProviderCredential
The tenant credential supplied to the provider.
Remarks
Section titled “Remarks”This is provider-specific by design. The shared AI.IChatClient
surface cannot expose an API key safely, so a provider must prove the
credential at its own observable boundary, such as a recording transport
or a vendor request factory. Checking only client identity is insufficient:
a new wrapper could be created while it still sends the setup-time key.
Concurrent_resolution_of_one_credential_stays_stable()
Section titled “ Concurrent_resolution_of_one_credential_stays_stable()”[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/Providers/ModelProviderCredentialContract.cs", 108)]public Task Concurrent_resolution_of_one_credential_stays_stable()Returns
Section titled “Returns”Remarks
Section titled “Remarks”A per-credential client cache is normally a
Concurrent.ConcurrentDictionary,
whose GetOrAdd may run its factory more than once for the same
key when threads race, discarding the extra results. Building a client
twice must therefore be harmless, and resolution must stay stable under
concurrency.
Create_chat_client_with_a_tenant_credential_returns_a_client()
Section titled “ Create_chat_client_with_a_tenant_credential_returns_a_client()”[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/Providers/ModelProviderCredentialContract.cs", 59)]public void Create_chat_client_with_a_tenant_credential_returns_a_client()Provider_declares_the_tenant_credential_capability()
Section titled “ Provider_declares_the_tenant_credential_capability()”[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/Providers/ModelProviderCredentialContract.cs", 55)]public void Provider_declares_the_tenant_credential_capability()Two_different_credentials_do_not_share_one_client()
Section titled “ Two_different_credentials_do_not_share_one_client()”[Fact("/Users/farukatasoy/Desktop/projects/Tracon/src/Tracon.Testing.Contracts.Xunit/Contracts/Providers/ModelProviderCredentialContract.cs", 91)]public void Two_different_credentials_do_not_share_one_client()Remarks
Section titled “Remarks”The credential, not the tenant, is the identity boundary: two distinct credentials must not resolve to one client, or one tenant’s key would serve another’s traffic.