Skip to content

Add framework-neutral transport-aware trigger identity validation - #218

Open
daviburg with Copilot wants to merge 14 commits into
mainfrom
copilot/add-framework-neutral-transport-overloads
Open

Add framework-neutral transport-aware trigger identity validation#218
daviburg with Copilot wants to merge 14 commits into
mainfrom
copilot/add-framework-neutral-transport-overloads

Conversation

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown

Description

Adds transport-aware overloads to ConnectorTriggerPayload that validate Connector Namespace trigger identity headers before payload deserialization — catching callback routing/configuration mistakes early with safe diagnostics, without coupling the core SDK to Azure Functions or ASP.NET Core.

Changes

  • ConnectorTriggerTransport — BCL-only wrapper (required Stream Body + IReadOnlyDictionary<string, IEnumerable<string>> Headers) for host-agnostic request representation. Callers adapt their host's request type locally.

  • ConnectorTriggerIdentity — Sealed record (ConnectorName, OperationName) declaring the expected callback identity.

  • ConnectorTriggerIdentityMismatchException — Thrown when identity headers are absent or mismatched. Exposes expected/actual connector+operation, which known identity headers were present, and correlation ID. Never exposes authorization headers, callback URLs, payload content, lock tokens, or other secrets.

  • ConnectorTriggerHeaderNames — Isolated constants for the three provisional Connector Namespace headers (x-ms-gateway-resource-name, x-ms-trigger-name, x-ms-client-request-id). Marked provisional; service team agreement required before treating these as stable contract.

  • ConnectorTriggerPayload.ReadAsync<TPayload>(ConnectorTriggerTransport, ConnectorTriggerIdentity, ...) — New overload. Validates headers (ordinal, case-insensitive for both name and value), then delegates to the existing bounded-stream deserializer. Existing Read<TPayload>(string) and ReadAsync<TPayload>(Stream, ...) are unchanged.

  • docs/triggers.md — Documents the transport overload, host-adapter pattern, exception diagnostics, and the provisional header contract table.

Usage (Azure Functions isolated worker example):

var transport = new ConnectorTriggerTransport
{
    Body    = request.Body,
    Headers = request.Headers.ToDictionary(h => h.Key, h => h.Value,
                  StringComparer.OrdinalIgnoreCase),
};

var payload = await ConnectorTriggerPayload
    .ReadAsync<Office365OnNewEmailTriggerPayload>(
        transport,
        new ConnectorTriggerIdentity(ConnectorNames.Office365, Office365TriggerOperations.OnNewEmail),
        cancellationToken: cancellationToken)
    .ConfigureAwait(continueOnCapturedContext: false);

Testing

  • Unit tests added/updated
  • All existing tests pass (dotnet test)
  • Manual testing (describe below if applicable)

16 new tests in ConnectorTriggerPayloadTransportTests: matching identity, missing connector/operation headers, both absent, connector mismatch, operation mismatch, case-insensitive header name/value lookup, correlation ID in exceptions, no-secrets guarantee on exception properties and message, PresentIdentityHeaderNames diagnostics, null argument guards, default empty headers, backward-compatibility of the existing Stream overload.

Checklist

  • Code follows the project's coding conventions
  • No modifications to files under src/**/Generated/ (see CONTRIBUTING.md)
  • Documentation updated (if behavior changed)
  • CHANGELOG.md updated (if user-facing change)
  • release_notes.md updated (if shipping a new version, clear previous version notes)
  • Version updated in eng/build/Version.props (if shipping a new version)

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

…on (#217)

- Add ConnectorTriggerTransport (Body stream + Headers dictionary, BCL-only)
- Add ConnectorTriggerIdentity sealed record (ConnectorName, OperationName)
- Add ConnectorTriggerIdentityMismatchException with safe diagnostics
- Add ConnectorTriggerHeaderNames provisional header name constants
- Add ReadAsync<TPayload>(ConnectorTriggerTransport, ConnectorTriggerIdentity, ...) overload
  to ConnectorTriggerPayload; validates identity headers before deserialization
- Add ConnectorTriggerPayloadTransportTests (16 new tests)
- Update docs/triggers.md with transport overload guide and provisional header table"
Copilot AI changed the title [WIP] Add framework-neutral transport overloads for validating trigger identity Add framework-neutral transport-aware trigger identity validation Jul 20, 2026
Copilot AI requested a review from daviburg July 20, 2026 18:36
@daviburg
daviburg marked this pull request as ready for review July 20, 2026 18:38
@daviburg
daviburg requested a review from a team as a code owner July 20, 2026 18:38
Copilot AI review requested due to automatic review settings July 20, 2026 18:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a framework-neutral way to validate Connector Namespace trigger callback identity (connector + operation) using request headers before deserializing the payload, enabling safer diagnostics and earlier detection of routing/configuration mistakes without coupling the SDK to any specific hosting framework.

Changes:

  • Introduces ConnectorTriggerTransport and ConnectorTriggerIdentity, plus ConnectorTriggerHeaderNames constants, to represent and validate trigger callbacks in a host-agnostic way.
  • Adds a new ConnectorTriggerPayload.ReadAsync<TPayload>(ConnectorTriggerTransport, ConnectorTriggerIdentity, ...) overload that validates identity headers before delegating to the existing bounded-stream deserializer.
  • Adds ConnectorTriggerIdentityMismatchException, documentation updates (docs/triggers.md), and a new test suite covering identity validation and safe diagnostics.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Azure.Connectors.Sdk.Tests/ConnectorTriggerPayloadTransportTests.cs Adds unit tests for transport-based identity validation behavior and exception diagnostics.
src/Azure.Connectors.Sdk/ConnectorTriggerTransport.cs Adds a BCL-only transport wrapper for body + headers.
src/Azure.Connectors.Sdk/ConnectorTriggerPayload.cs Adds transport overload plus identity validation and header-value extraction helpers.
src/Azure.Connectors.Sdk/ConnectorTriggerIdentityMismatchException.cs Adds a dedicated exception type exposing only safe identity diagnostics.
src/Azure.Connectors.Sdk/ConnectorTriggerIdentity.cs Adds a record representing the expected connector/operation identity.
src/Azure.Connectors.Sdk/ConnectorTriggerHeaderNames.cs Adds constants for the (provisional) Connector Namespace identity headers.
docs/triggers.md Documents the new overload, adapter pattern, exception diagnostics, and header contract table.

Comment on lines +315 to +333
private static string? GetFirstHeaderValue(
IReadOnlyDictionary<string, IEnumerable<string>> headers,
string headerName)
{
if (!headers.TryGetValue(headerName, out IEnumerable<string>? values))
{
return null;
}

foreach (string value in values)
{
if (!string.IsNullOrEmpty(value))
{
return value;
}
}

return null;
}
expectedOperationName: expectedIdentity.OperationName,
actualConnectorName: actualConnectorName,
actualOperationName: actualOperationName,
presentIdentityHeaderNames: presentHeaders,
Comment on lines +104 to +105
// Arrange — headers keyed with upper-case names in a plain Dictionary; the
// ConnectorTriggerTransport default comparer normalises lookup.
@daviburg

Copy link
Copy Markdown
Member

[Dobby] The header-only identity mapping here is not valid: x-ms-gateway-resource-name and x-ms-trigger-name are the Connector Namespace and trigger-config resource names, not the connector API name and Swagger operation name. As a result, a real callback such as gateway my-gateway / trigger config email-trigger will always fail comparison with office365 / OnNewEmailV3 before deserialization.

I updated issue #217 with the required end-to-end design. The validating overload must parse the four resource-context headers, use an authenticated Connector Namespace trigger-config GET to retrieve properties.connectionDetails.connectorName and properties.operationName, compare those authoritative fields to the caller-selected typed payload identity, and only then deserialize. Please rework this PR to that specification; it also calls for a framework-neutral resolver abstraction plus the SDK's standard TokenCredential-based management implementation, realistic service-header tests, and SDK-owned case-insensitive header-name lookup.

@daviburg

Copy link
Copy Markdown
Member

@copilot review comments on this PR: issue has been updated to close the specification gap from current available headers to connector name + operation name pair. Rework the proposed implementation accordingly.

Copilot AI requested review from Copilot and removed request for Copilot July 20, 2026 19:00
Copilot AI requested review from Copilot and removed request for Copilot July 20, 2026 19:01
Copilot AI requested review from Copilot and removed request for Copilot July 20, 2026 19:02
Copilot AI requested review from Copilot and removed request for Copilot July 20, 2026 19:02
Copilot AI removed the request for review from Copilot July 20, 2026 20:18
Copilot AI requested review from Copilot and removed request for Copilot July 20, 2026 20:19
Copilot AI requested review from Copilot and removed request for Copilot July 20, 2026 20:22

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Author

@copilot fix your build break

Fixed in 5156777.

@daviburg

Copy link
Copy Markdown
Member

@copilot investigate CI gate failure 'Code quality findings with severity of warning or above were detected.'

Copilot AI requested review from Copilot and removed request for Copilot July 20, 2026 20:39

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Author

@copilot investigate CI gate failure 'Code quality findings with severity of warning or above were detected.'

Investigated and fixed the CI gate trigger in 614c4ef. The failing signal was the GitHub code-quality warning on ConnectorNamespaceTriggerConfigManagementResolverTests; I replaced the tuple-returned request getter delegate with a direct request holder so the test keeps the same assertions without tripping the warning.

Copilot AI review requested due to automatic review settings July 28, 2026 16:23
@daviburg

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

docs/triggers.md:157

  • The transport-adapter example uses single-letter lambda parameters (h => ...). The repo coding conventions require descriptive lambda parameter names (no single-letter identifiers), and keeping docs/examples consistent helps consumers copy/paste compliant code.
    Headers = request.Headers.ToDictionary(
                  h => h.Key,
                  h => h.Value,
                  StringComparer.OrdinalIgnoreCase),

src/Azure.Connectors.Sdk/ConnectorTriggerTransport.cs:33

  • The XML-doc example uses single-letter lambda parameters (h => ...). The repo coding conventions require descriptive lambda parameter names (no single-letter identifiers), and this snippet is likely to be copied into user code.
///         .ToDictionary(
///             h => h.Key,
///             h => h.Value,
///             StringComparer.OrdinalIgnoreCase)

src/Azure.Connectors.Sdk/ConnectorNamespaceTriggerConfigManagementResolver.cs:71

  • Audience and ApiVersion are validated for whitespace-only but then used without trimming. Leading/trailing whitespace will produce an invalid token scope (and potentially an invalid api-version query), causing hard-to-diagnose auth/request failures. Consider trimming before storing/using these values.
        this._managementEndpoint = options.ManagementEndpoint;
        this._apiVersion = options.ApiVersion;
        this._audienceScopes = new[] { $"{options.Audience.TrimEnd('/')}/.default" };

@daviburg daviburg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] Review — Logic Apps senior review lens

Reviewed the server-side diff at d4c615e, plus the repo conventions in .github/copilot-instructions.md, Directory.Packages.props, Azure.Connectors.Sdk.csproj, and docs/. Findings are ordered correctness/risk first, then tests, then fan-out and style. Inline comments cover the file-anchored findings; two repo-level findings are below.

Generated by an agent under my identity, applying repo conventions and a distilled review checklist.

Top four (blocking)

  1. Uncached ARM GET on every callbackConnectorTriggerPayload.ReadAsync was CPU-only deserialization; it now takes a synchronous dependency on the management plane in the trigger hot path, with no cache. ARM read throttling is per-subscription per-hour, so a busy endpoint will 429 and fail callbacks. See inline on ConnectorNamespaceTriggerConfigManagementResolver.GetTriggerConfigAsync.
  2. Security framing in docs/triggers.md — the resource identity is built entirely from unauthenticated request headers, so this is a misconfiguration guard, not defence-in-depth. See inline on docs/triggers.md.
  3. Management-endpoint timeouts surface as OperationCanceledException rather than ConnectorTriggerConfigurationResolutionException. Two sites, same defect.
  4. ConnectorTriggerConfigurationResolutionException.CorrelationId is always null on the realistic failure paths (404/401/403/malformed), and the documented catch handler logs it.

Repo-level findings (no file anchor)

[Medium] The PR description no longer matches the diff.

The description describes a header-only comparison of three provisional headers. The diff ships five headers and eight new public types the description never mentions: ConnectorNamespaceTriggerConfig, ConnectorNamespaceTriggerConfigManagementResolver, ConnectorNamespaceTriggerConfigManagementResolverOptions, ConnectorNamespaceTriggerConfigResourceIdentity, IConnectorNamespaceTriggerConfigResolver, ConnectorTriggerConfigurationResolutionException, ConnectorTriggerResourceIdentityException. Specifics:

  • The usage sample in the description omits the now-required resolver argument and will not compile.
  • It claims "16 new tests in ConnectorTriggerPayloadTransportTests". That file has 12 [TestMethod]s; a second, unmentioned file adds 6 more.
  • It names a property PresentIdentityHeaderNames; the shipped property is PresentResourceIdentityHeaderNames.
  • It claims tests for null argument guards, default empty headers, and case-insensitive header value lookup. None of those exist in the diff.

The design pivoted mid-review from header-trust to authoritative-config-resolution — a materially different security and availability posture. Please rewrite the description in the same iteration so the approval trail records the design that actually shipped.

[Medium] CHANGELOG.md and docs/azure-sdk-guidelines.md fan-out is missing.

The checklist leaves CHANGELOG.md updated unchecked while adding eight public types and a public overload. CHANGELOG.md [Unreleased] is cut verbatim into release_notes.md at release time, so this would ship a release with undocumented public API — please add an ### Added entry.

.github/copilot-instructions.md states: "Before adding new public API surface, check that document [docs/azure-sdk-guidelines.md]." A new ClientOptions-derived options type, a new TokenCredential-taking client, and a new resolver interface all land in its scope. Either record the new surface there or note explicitly why no entry is needed.

Confirmed clean

  • No package fan-out needed — Azure.Core and Azure.Identity were already referenced by Azure.Connectors.Sdk.csproj.
  • The docs/triggers.md sample constants are accurate: OneDriveForBusinessTriggerOperations.OnNewFiles resolves to "OnNewFilesV2".
  • presentHeaders is correctly wrapped via AsReadOnly(), so the earlier mutable-downcast concern is addressed.

Residual risk

  • Two connectors-sdk.public ADO legs were still pending at review time (10 green, 0 failing).
  • I did not build or run tests locally; all analysis is from the server-side diff at d4c615e.
  • The unresolved github-code-quality[bot] finding on a useless local assignment in ConnectorNamespaceTriggerConfigManagementResolverTests.cs was not re-verified.

}

/// <inheritdoc />
public async ValueTask<ConnectorNamespaceTriggerConfig> GetTriggerConfigAsync(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [High] [Architecture / Availability] Every trigger callback now performs an uncached ARM GET.

This method builds a fresh pipeline message and issues GET .../connectorGateways/{ns}/triggerconfigs/{name}?api-version=... on every invocation. There is no cache, no memoization, and no TTL anywhere in the PR, and ConnectorTriggerPayload.ReadAsync calls it before every deserialization.

Why it matters: ConnectorTriggerPayload was a pure, CPU-only deserialization utility. The new overload turns it into a synchronous dependency on the management plane in the trigger hot path. ARM read throttling is per-subscription per-hour, so a moderately busy trigger endpoint will hit 429, which this code converts into ConnectorTriggerConfigurationResolutionException and fails the callback. Trigger config for a given resource identity is effectively immutable per deployment, so the round trip is pure overhead — one management call and one token check per inbound event.

Worth noting that IConnectorNamespaceTriggerConfigResolver.GetTriggerConfigAsync already returns ValueTask — the shape that exists specifically to allow a cached synchronous return — but nothing caches.

Expected fix: add a caching decorator (or cache inside this resolver) keyed on the four-part resource identity, with a bounded entry count and a TTL, and document the invalidation story. Please ship the caching resolver in this PR so the default path is not one ARM call per callback.

.SendAsync(message, cancellationToken)
.ConfigureAwait(continueOnCapturedContext: false);
}
catch (OperationCanceledException)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [High] [Defensive code] Management-endpoint timeouts will surface as cancellation, not as a resolution failure.

Azure.Core surfaces HTTP timeouts as TaskCanceledException, which derives from OperationCanceledException. This bare rethrow therefore also catches genuine transport timeouts where the caller's token was never cancelled.

Why it matters: the caller sees OperationCanceledException instead of ConnectorTriggerConfigurationResolutionException. The handler documented in docs/triggers.md will not catch it, hosts commonly treat OperationCanceledException as shutdown/abort rather than a retryable failure, and the callback failure is misattributed.

Expected fix: guard on the caller token so real timeouts fall through to the wrapping catch:

catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
    throw;
}

Separately: the "don't catch-rethrow for nothing" thread on ConnectorTriggerPayload.cs was addressed in aec6b20 by moving to an exception filter, but this sibling occurrence was not swept. Applying the token guard here resolves both concerns and makes the two files use one shape for one concern.

resourceIdentity: resourceIdentity,
status: response.Status,
correlationId: null,
detail: "The management API returned an unsuccessful status while resolving the trigger configuration.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [High] [Diagnostics contract] CorrelationId is hardcoded to null at every throw site in this resolver.

All four CreateResolutionException calls in this file pass correlationId: null, because IConnectorNamespaceTriggerConfigResolver.GetTriggerConfigAsync has no parameter through which the callback correlation ID could reach the resolver. ConnectorTriggerPayload.ReadAsync then explicitly excludes ConnectorTriggerConfigurationResolutionException from re-wrapping, so these exceptions propagate to the caller unchanged.

Why it matters: 404 / 401 / 403 / malformed-response from ARM are the realistic failures, and every one of them loses the correlation ID that the diagnostics story is built around. The catch handler documented in docs/triggers.md logs ex.CorrelationId for exactly this exception type and will always print null.

Expected fix: plumb the correlation ID into the resolver (extend ConnectorNamespaceTriggerConfigResourceIdentity, or add a context parameter to the interface), and also forward it as x-ms-client-request-id on the outbound ARM request so the management-side and callback-side traces join up.

detail: "The management API returned an unsuccessful status while resolving the trigger configuration.");
}

string responseContent = response.Content.ToString();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [Low] [Performance] response.Content.ToString() materializes the whole ARM response as a string before parsing.

The string is used only for an emptiness check and then handed to JsonDocument.Parse. JsonDocument can parse UTF-8 bytes directly.

Expected fix: parse from response.Content.ToMemory() (or response.ContentStream) and replace the IsNullOrWhiteSpace check with a length check on the memory. That drops one full-payload allocation and the UTF-8 to UTF-16 transcode per callback — which matters more given this now runs on every inbound event (see the caching comment on GetTriggerConfigAsync).

private const string TriggerConfigsSegmentName = "triggerconfigs";

private readonly string _apiVersion;
private readonly string[] _audienceScopes;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [Low] [Dead code] _audienceScopes is written in the constructor and never read again.

Its only consumer is the BearerTokenAuthenticationPolicy construction a few lines below, inside the same constructor. Promoting it to a field implies per-instance state that is later reused, which is misleading.

Expected fix: make it a local.

Comment thread docs/triggers.md
string.Join(", ", ex.PresentResourceIdentityHeaderNames));
throw;
}
catch (ConnectorTriggerConfigurationResolutionException ex)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [High] [Docs / Diagnostics] This handler will always log null for CorrelationId.

ConnectorNamespaceTriggerConfigManagementResolver passes correlationId: null at all four of its throw sites, and ConnectorTriggerPayload.ReadAsync explicitly excludes ConnectorTriggerConfigurationResolutionException from re-wrapping — so on the realistic ARM failure paths (404 / 401 / 403 / malformed response) this exception reaches the caller with a null correlation ID.

The one test asserting a non-null CorrelationId for this exception (ReadAsync_Transport_ResolverFailure_…) uses a stub throwing InvalidOperationException, so it exercises the wrapping path in ConnectorTriggerPayload and never the resolver's own throws. It will keep passing while this documented handler stays broken.

Expected fix: plumb the correlation ID through the resolver contract (see the inline comment on IConnectorNamespaceTriggerConfigResolver), then add a test that drives a real ConnectorNamespaceTriggerConfigManagementResolver returning 404 and asserts CorrelationId is populated.

public async Task GetTriggerConfigAsync_MalformedResponse_ThrowsConfigurationResolutionException()
{
// Arrange
const string malformedJson = "{\"properties\":{\"connectionDetails\":{}}}";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [Medium] [Tests] This payload is well-formed JSON, so the catch (JsonException) branch is never exercised.

{"properties":{"connectionDetails":{}}} parses cleanly and then fails the property checks in ParseTriggerConfig, which is the "did not contain required trigger configuration properties" path — a different branch from the one the test name implies.

Expected fix: keep this case (rename it to something like _MissingRequiredProperties_) and add a genuinely malformed case, for example "{not json", asserting the JsonException branch and that the inner exception is preserved.

Two more uncovered branches in this file's SUT while you are here:

  • The empty-response-body path (string.IsNullOrWhiteSpace(responseContent)).
  • The "contained empty trigger configuration identity values" path — present-but-empty connectorName or operationName, for example {"properties":{"operationName":"","connectionDetails":{"connectorName":"onedriveforbusiness"}}}.

.ConfigureAwait(continueOnCapturedContext: false);
}

private static Mock<TokenCredential> CreateCredential()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [Medium] [Tests] No coverage for any of the four new constructor guards.

ConnectorNamespaceTriggerConfigManagementResolver's constructor validates four conditions, none of which are tested:

  • null credentialArgumentNullException
  • relative options.ManagementEndpointArgumentException
  • null or whitespace options.AudienceArgumentException
  • null or whitespace options.ApiVersionArgumentException

These are new public contract surfaces and all four are reachable directly through the public constructor, so no product-code changes are needed to cover them.

Also missing here: a URI-escaping case. The one URI assertion in this file uses resource names that need no escaping, so BuildRequestUri's Uri.EscapeDataString calls are effectively untested. A resource group or trigger-config name containing a space or a reserved character would lock that behavior in.

}

[TestMethod]
public async Task ReadAsync_Transport_ResolverFailure_ThrowsConfigurationResolutionException()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [Medium] [Tests] This test asserts on the wrapping path, not the resolver path it appears to cover.

The stub throws InvalidOperationException, so the assertion Assert.AreEqual("trace-xyz", exception.CorrelationId) passes because ConnectorTriggerPayload.CreateConfigurationResolutionException supplies the correlation ID. The real ConnectorNamespaceTriggerConfigManagementResolver throws ConnectorTriggerConfigurationResolutionException itself with correlationId: null, and ReadAsync's catch filter deliberately does not re-wrap that type — so the shipped behavior on the realistic ARM failure paths is the opposite of what this test demonstrates.

Mutation check: change the resolver's correlationId: null to any other value and this test does not notice, because it never runs that code.

Expected fix: add a companion case where the stub throws ConnectorTriggerConfigurationResolutionException directly, asserting what actually reaches the caller. Then fix the underlying gap (see the inline comment on IConnectorNamespaceTriggerConfigResolver).

}

[TestMethod]
public async Task ReadAsync_Transport_ResolvedIdentityMatches_ReturnsPayload()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Dobby] [Medium] [Tests] Claimed tests that are not in the diff, plus uncovered branches on the new overload.

The PR description claims tests for null argument guards, default empty headers, case-insensitive header value lookup, and PresentIdentityHeaderNames diagnostics. None are present in this file (and the shipped property is PresentResourceIdentityHeaderNames). The description also says 16 tests here; there are 12.

Specific cases worth adding, all reachable through the existing public API:

  • ArgumentNullException for each of transport, expectedIdentity, triggerConfigResolver.
  • ConnectorTriggerTransport with the default Headers (construct with only Body set) — should throw ConnectorTriggerResourceIdentityException with an empty PresentResourceIdentityHeaderNames.
  • A header present but whitespace-only — should be treated as absent, and the header name should be excluded from PresentResourceIdentityHeaderNames.
  • A header value with surrounding whitespace — should be trimmed before being placed in the resource identity, asserted via resolver.LastRequestedResourceIdentity.
  • The resolver returning null — should throw ConnectorTriggerConfigurationResolutionException with the "returned a null trigger configuration" detail.
  • maxBodySizeBytes on the new overload: a body over the limit, and an invalid value of 0.
  • Case-insensitive value comparison, since ValidateIdentity uses OrdinalIgnoreCase on values as well as names — for example a resolved connector of "OneDriveForBusiness" against an expected "onedriveforbusiness" should pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add end-to-end trigger-config validation before typed callback deserialization

3 participants