Skip to content

feat(sdk): add .NET SDK with AWS SSM and Azure Key Vault support#146

Merged
macalbert merged 11 commits intomainfrom
macalbert/sdk-dotnet
Apr 8, 2026
Merged

feat(sdk): add .NET SDK with AWS SSM and Azure Key Vault support#146
macalbert merged 11 commits intomainfrom
macalbert/sdk-dotnet

Conversation

@macalbert
Copy link
Copy Markdown
Owner

@macalbert macalbert commented Apr 8, 2026

Summary

Adds the .NET SDK for Envilder, enabling .NET applications to load secrets directly from AWS SSM Parameter Store or Azure Key Vault using the standard map-file format. Also includes shared Copilot skills for .NET architecture, testing, security, and observability, plus a NuGet publish workflow.

Changes

SDK — .NET (src/sdks/dotnet/)

  • EnvilderClient application service: parses map file and resolves secrets via provider
  • MapFileParser for reading and validating the JSON map-file format
  • SecretProviderFactory with enum-based provider selection (Aws / Azure)
  • Domain layer: EnvilderOptions, MapFileConfig, ParsedMapFile, ISecretProvider port, SecretProviderType enum
  • Infrastructure adapters: AwsSsmSecretProvider (AWS SDK), AzureKeyVaultSecretProvider (Azure SDK)
  • ASP.NET Core integration: IConfigurationBuilder.AddEnvilder() extension, IServiceCollection.AddEnvilder() extension
  • Custom EnvilderConfigurationProvider and EnvilderConfigurationSource for seamless IConfiguration integration

Tests (tests/sdks/dotnet/)

  • Unit tests for EnvilderClient, MapFileParser, SecretProviderFactory
  • Unit tests for AwsSsmSecretProvider, AzureKeyVaultSecretProvider
  • Unit tests for ConfigurationBuilderExtensions, EnvilderConfigurationProvider, ServiceCollectionExtensions
  • Acceptance tests: AwsSsmAcceptanceTests (LocalStack via TestContainers), AzureKeyVaultAcceptanceTests (Lowkey Vault via TestContainers)
  • End-to-end ConsumerExperienceTests verifying the full load-secrets pipeline
  • Shared fixtures: LocalStackFixture, LowkeyVaultFixture, ContainersCollection

CI/CD

  • publish-nuget.yml workflow for publishing to NuGet on release tags

Copilot Skills (.github/skills/)

  • common-environments, common-git, common-observability, common-security
  • common-testing-conventions (with dotnet, python, typescript sub-files)
  • dotnet-architecture, dotnet-dependency-injection, dotnet-integration-testing
  • dotnet-libraries, dotnet-test-doubles
  • python-testing, typescript-testing

Testing

  • dotnet test passes (unit + acceptance + e2e)
  • pnpm test passes (TypeScript core unchanged)
  • pnpm lint passes
  • Manual verification with LocalStack and Lowkey Vault via TestContainers

Related

Implements .NET SDK from the Envilder SDK platform plan.


Open with Devin

Summary by CodeRabbit

  • New Features

    • Added a .NET SDK to resolve secrets and surface them via process environment, IConfiguration integration, or dependency injection; supports AWS SSM and Azure Key Vault.
  • Documentation

    • Added SDK README with installation, integration examples, and map-file format.
  • Tests

    • Added comprehensive unit, integration, and end-to-end tests covering parsing, providers, configuration integration, and DI.
  • Chores

    • Added .NET project and test project configs; removed placeholder files.

Implement the Envilder .NET SDK targeting netstandard2.0 with hexagonal
architecture (Domain/Application/Infrastructure). Includes map-file
parsing, secret resolution, IConfiguration and IServiceCollection
integration, plus unit and acceptance tests using LocalStack and
Lowkey Vault via Testcontainers.
Implement the Envilder .NET SDK targeting netstandard2.0 with hexagonal
architecture (Domain/Application/Infrastructure). Includes map-file
parsing, secret resolution, IConfiguration and IServiceCollection
integration, plus unit and acceptance tests using LocalStack and
Lowkey Vault via Testcontainers.
Replaces string provider selection with SecretProviderType enum for
strong typing and clarity. Refactors SecretProviderFactory and parsing
logic accordingly. Updates tests to use the enum and improves LocalStack
test reliability with health checks and embedded secrets.
Add comprehensive XML documentation for all public APIs to improve
discoverability and maintainability. Introduce end-to-end tests using
LocalStack and Lowkey Vault to verify integration with AWS SSM and Azure
Key Vault, including configuration and error handling scenarios. Update
test dependencies to Microsoft.Extensions.* v10 for .NET 8 support.
Allow MapFileConfig and ParsedMapFile properties to be set after
construction. Remove IsExternalInit.cs as it is no longer needed.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 13475c3c-bdd6-4857-90c8-1451d007d0f0

📥 Commits

Reviewing files that changed from the base of the PR and between 1bbdc36 and 7d8d049.

📒 Files selected for processing (5)
  • src/sdks/dotnet/Infrastructure/Aws/AwsSsmSecretProvider.cs
  • src/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationProvider.cs
  • src/sdks/dotnet/Infrastructure/SecretProviderFactory.cs
  • tests/sdks/dotnet/Fixtures/LocalStackFixture.cs
  • tests/sdks/dotnet/Infrastructure/DependencyInjection/ServiceCollectionExtensionsTests.cs

Walkthrough

Adds a .NET Envilder SDK: JSON map-file parser, secret-resolution client, AWS/Azure secret providers and factory, IConfiguration/IServiceCollection integrations, project/solution files, README, and extensive unit, acceptance, and end-to-end tests.

Changes

Cohort / File(s) Summary
Domain Models
src/sdks/dotnet/Domain/SecretProviderType.cs, src/sdks/dotnet/Domain/MapFileConfig.cs, src/sdks/dotnet/Domain/ParsedMapFile.cs, src/sdks/dotnet/Domain/EnvilderOptions.cs, src/sdks/dotnet/Domain/Ports/ISecretProvider.cs
Added domain types: provider enum, map-file config, parsed map container, runtime options, and ISecretProvider interface.
Application Layer
src/sdks/dotnet/Application/MapFileParser.cs, src/sdks/dotnet/Application/EnvilderClient.cs
Added JSON map-file parser and EnvilderClient to resolve secrets via ISecretProvider and inject into the process environment.
Secret Providers & Factory
src/sdks/dotnet/Infrastructure/Aws/AwsSsmSecretProvider.cs, src/sdks/dotnet/Infrastructure/Azure/AzureKeyVaultSecretProvider.cs, src/sdks/dotnet/Infrastructure/SecretProviderFactory.cs
Implemented AWS SSM and Azure Key Vault providers plus SecretProviderFactory to select/construct providers (profile and vault URL resolution and related errors).
Configuration Integration
src/sdks/dotnet/Infrastructure/Configuration/ConfigurationBuilderExtensions.cs, src/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationSource.cs, src/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationProvider.cs
IConfigurationBuilder extension and IConfigurationSource/Provider that resolve secrets and populate configuration (normalizes slash-delimited keys).
Dependency Injection
src/sdks/dotnet/Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs
IServiceCollection extension to register EnvilderClient and parsed map-file as singletons from a map file and ISecretProvider.
Project & Packaging
src/sdks/dotnet/Envilder.csproj, src/sdks/dotnet/Envilder.sln
New .NET project and solution with NuGet metadata and dependencies (AWS, Azure, Microsoft.Extensions, System.Text.Json).
SDK Docs & placeholders
src/sdks/dotnet/README.md, src/sdks/dotnet/.gitkeep, tests/sdks/dotnet/.gitkeep
Added README; removed placeholder .gitkeep files.
Tests: Unit / Integration / E2E
tests/sdks/dotnet/**/*.cs, tests/sdks/dotnet/Envilder.Tests.csproj, tests/sdks/dotnet/secrets-map.json
Comprehensive tests added: unit tests for parser, client, providers, configuration and DI extensions, factory; acceptance tests using LocalStack and Lowkey Vault; end-to-end consumer tests; test project config and sample secrets map.
Test Fixtures & Utilities
tests/sdks/dotnet/Fixtures/*, tests/sdks/dotnet/CancellationTokenForTest.cs, tests/sdks/dotnet/Infrastructure/Aws/LocalStackHealthCheck.cs
Container fixtures for LocalStack and Lowkey Vault, health-check helper, and shared timeout constants for tests.

Sequence Diagram(s)

sequenceDiagram
    participant App as App
    participant Parser as MapFileParser
    participant Parsed as ParsedMapFile
    participant Factory as SecretProviderFactory
    participant Provider as ISecretProvider
    participant Client as EnvilderClient

    App->>Parser: Parse(json map file)
    Parser-->>App: ParsedMapFile
    App->>Factory: Create(config, options?)
    Factory-->>App: ISecretProvider (AWS/Azure)
    App->>Client: new EnvilderClient(provider)
    App->>Client: ResolveSecretsAsync(ParsedMapFile)
    loop per mapping
        Client->>Provider: GetSecretAsync(path)
        Provider-->>Client: secret value or null
    end
    Client-->>App: Dictionary<key,value> (nulls omitted)
    App->>Client: InjectIntoEnvironment(secrets)
    loop per secret
        Client->>App: Environment.SetEnvironmentVariable(key, value)
    end
Loading
sequenceDiagram
    participant App as .NET App
    participant ConfigBuilder as IConfigurationBuilder
    participant Ext as AddEnvilder
    participant Source as EnvilderConfigurationSource
    participant Provider as EnvilderConfigurationProvider
    participant Client as EnvilderClient
    participant SecretProvider as ISecretProvider

    App->>Ext: AddEnvilder(mapFilePath, secretProvider)
    Ext->>ConfigBuilder: builder.Add(source)
    App->>ConfigBuilder: Build()
    ConfigBuilder->>Provider: provider.Load()
    Provider->>Client: ResolveSecretsAsync(mapFile)
    loop per mapping
        Client->>SecretProvider: GetSecretAsync(path)
        SecretProvider-->>Client: value/null
    end
    Client-->>Provider: resolved secrets
    Provider->>ConfigBuilder: populate Data (normalized keys)
    ConfigBuilder-->>App: IConfiguration ready
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

documentation

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description is comprehensive and well-structured, covering summary, changes, testing, and related work. However, it does not follow the template structure with explicit sections like 'What does this PR do?', 'Related issues', 'Type of change' checkboxes, or reviewer notes. Consider reorganizing the description to match the template format: add explicit 'What does this PR do?' section, 'Related issues' section, check the appropriate 'Type of change' checkbox, and include 'Notes for reviewer' if applicable.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: adding a .NET SDK with specific provider support (AWS SSM and Azure Key Vault).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch macalbert/sdk-dotnet

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@macalbert macalbert requested a review from Copilot April 8, 2026 17:19
@macalbert macalbert self-assigned this Apr 8, 2026
@macalbert macalbert added the enhancement New feature or request label Apr 8, 2026
gemini-code-assist[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
src/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationProvider.cs (1)

12-16: Consider adding null validation for constructor parameters.

If client or mapFile is null, the Load() method will throw a NullReferenceException with minimal context. Adding guard clauses improves debuggability.

🛡️ Proposed defensive validation
 public EnvilderConfigurationProvider(EnvilderClient client, ParsedMapFile mapFile)
 {
-    _client = client;
-    _mapFile = mapFile;
+    _client = client ?? throw new ArgumentNullException(nameof(client));
+    _mapFile = mapFile ?? throw new ArgumentNullException(nameof(mapFile));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationProvider.cs`
around lines 12 - 16, Add defensive null validation in the
EnvilderConfigurationProvider constructor: check that the incoming
EnvilderClient parameter (client) and ParsedMapFile parameter (mapFile) are not
null and throw ArgumentNullException with the appropriate parameter name if they
are; this prevents obscure NullReferenceExceptions later in the Load() method
and makes failures clearer by validating before assigning to the _client and
_mapFile fields.
tests/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationSectionBindingTests.cs (1)

96-101: Consider using int for MaxPoolSize to test type conversion.

MaxPoolSize is declared as string, but in real-world scenarios it would typically be an int. The current test verifies string binding works, but doesn't exercise the configuration system's type conversion. If this is intentional for simplicity, it's fine; otherwise, using the actual expected type would provide additional coverage.

💡 Optional: Test numeric binding
 private class DatabaseConfig
 {
     public const string SectionName = "Database";
     public string ConnectionString { get; set; } = string.Empty;
-    public string MaxPoolSize { get; set; } = string.Empty;
+    public int MaxPoolSize { get; set; }
 }

Then update the assertion:

-actual.MaxPoolSize.Should().Be("100");
+actual.MaxPoolSize.Should().Be(100);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationSectionBindingTests.cs`
around lines 96 - 101, The test's DatabaseConfig declares MaxPoolSize as string
which doesn't exercise configuration type conversion; change
DatabaseConfig.MaxPoolSize to an int (e.g., public int MaxPoolSize { get; set;
}) with a sensible default, update the test's configuration input to provide a
numeric value (e.g., "10"), and adjust the assertion to expect the bound int
value (e.g., Assert.Equal(10, bound.Database.MaxPoolSize)); keep SectionName and
other properties unchanged so the binding call that uses DatabaseConfig (e.g.,
ConfigurationBinder/Bind/GetSection in the test) verifies numeric conversion.
tests/sdks/dotnet/Infrastructure/SecretProviderFactoryTests.cs (2)

59-70: Consider verifying the exception message.

The test confirms InvalidOperationException is thrown but doesn't assert on the message content. Verifying the message would catch regressions where the exception type is correct but the context is wrong.

Suggested enhancement
         // Assert
-        act.Should().Throw<InvalidOperationException>();
+        act.Should().Throw<InvalidOperationException>()
+            .WithMessage("*Vault URL*");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/dotnet/Infrastructure/SecretProviderFactoryTests.cs` around lines
59 - 70, Update the test Should_RequireVaultUrl_When_AzureProviderSelected to
also assert the exception message content: when invoking
SecretProviderFactory.Create with a MapFileConfig whose Provider is
SecretProviderType.Azure, capture the InvalidOperationException and assert its
Message contains the expected text (e.g., mentions missing VaultUrl or similar)
so the test verifies both type and context; reference the existing act delegate
and replace the simple Throw assertion with one that inspects the
exception.Message returned by SecretProviderFactory.Create.

9-70: Add test coverage for null config and invalid AWS profile scenarios.

The test suite covers the happy paths well but lacks edge cases:

  1. Null config parameter (once ArgumentNullException validation is added)
  2. Invalid/missing AWS profile when profile is explicitly specified (once fail-fast behavior is implemented)

These tests would ensure the factory's defensive behaviors are exercised.

Suggested additional tests
[Fact]
public void Should_ThrowArgumentNullException_When_ConfigIsNull()
{
    // Act
    var act = () => SecretProviderFactory.Create(null!);

    // Assert
    act.Should().Throw<ArgumentNullException>()
        .And.ParamName.Should().Be("config");
}

[Fact]
public void Should_ThrowInvalidOperationException_When_AwsProfileNotFound()
{
    // Arrange
    var config = new MapFileConfig
    {
        Provider = SecretProviderType.Aws,
        Profile = "nonexistent-profile-12345",
    };

    // Act
    var act = () => SecretProviderFactory.Create(config);

    // Assert
    act.Should().Throw<InvalidOperationException>()
        .WithMessage("*profile*nonexistent-profile-12345*");
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/dotnet/Infrastructure/SecretProviderFactoryTests.cs` around lines
9 - 70, Add two edge-case unit tests in SecretProviderFactoryTests to validate
defensive behavior: (1) a Should_ThrowArgumentNullException_When_ConfigIsNull
test that calls SecretProviderFactory.Create(null!) and asserts an
ArgumentNullException is thrown with ParamName "config"; and (2) a
Should_ThrowInvalidOperationException_When_AwsProfileNotFound test that creates
a MapFileConfig with Provider = SecretProviderType.Aws and Profile =
"nonexistent-profile-12345", calls SecretProviderFactory.Create(config) and
asserts an InvalidOperationException is thrown with a message containing the
missing profile string; locate and add these tests alongside the existing tests
referencing SecretProviderFactory.Create, MapFileConfig and SecretProviderType.
src/sdks/dotnet/Infrastructure/SecretProviderFactory.cs (1)

45-45: Consider improving readability of nested constructor calls.

The triple-nested target-typed new() is syntactically valid but harder to parse at a glance. Explicit type names or intermediate variables would clarify intent.

Suggested refactor
-        return new(new(new(vaultUrl), new DefaultAzureCredential()));
+        var secretClient = new SecretClient(new Uri(vaultUrl), new DefaultAzureCredential());
+        return new AzureKeyVaultSecretProvider(secretClient);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sdks/dotnet/Infrastructure/SecretProviderFactory.cs` at line 45, The
return statement in SecretProviderFactory that uses triple-nested target-typed
new (return new(new(new(vaultUrl), new DefaultAzureCredential()));) is hard to
read; refactor by introducing intermediate variables or using explicit type
constructors to build the inner Vault/credential objects before returning the
outer object (e.g., create a DefaultAzureCredential instance, create the
VaultClient/Credential-wrapping object with vaultUrl, then pass those to the
outer constructor) and replace the nested new(...) chain with those variables or
explicit type-named new(...) calls to improve clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationProvider.cs`:
- Around line 12-16: Add defensive null validation in the
EnvilderConfigurationProvider constructor: check that the incoming
EnvilderClient parameter (client) and ParsedMapFile parameter (mapFile) are not
null and throw ArgumentNullException with the appropriate parameter name if they
are; this prevents obscure NullReferenceExceptions later in the Load() method
and makes failures clearer by validating before assigning to the _client and
_mapFile fields.

In `@src/sdks/dotnet/Infrastructure/SecretProviderFactory.cs`:
- Line 45: The return statement in SecretProviderFactory that uses triple-nested
target-typed new (return new(new(new(vaultUrl), new DefaultAzureCredential()));)
is hard to read; refactor by introducing intermediate variables or using
explicit type constructors to build the inner Vault/credential objects before
returning the outer object (e.g., create a DefaultAzureCredential instance,
create the VaultClient/Credential-wrapping object with vaultUrl, then pass those
to the outer constructor) and replace the nested new(...) chain with those
variables or explicit type-named new(...) calls to improve clarity.

In
`@tests/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationSectionBindingTests.cs`:
- Around line 96-101: The test's DatabaseConfig declares MaxPoolSize as string
which doesn't exercise configuration type conversion; change
DatabaseConfig.MaxPoolSize to an int (e.g., public int MaxPoolSize { get; set;
}) with a sensible default, update the test's configuration input to provide a
numeric value (e.g., "10"), and adjust the assertion to expect the bound int
value (e.g., Assert.Equal(10, bound.Database.MaxPoolSize)); keep SectionName and
other properties unchanged so the binding call that uses DatabaseConfig (e.g.,
ConfigurationBinder/Bind/GetSection in the test) verifies numeric conversion.

In `@tests/sdks/dotnet/Infrastructure/SecretProviderFactoryTests.cs`:
- Around line 59-70: Update the test
Should_RequireVaultUrl_When_AzureProviderSelected to also assert the exception
message content: when invoking SecretProviderFactory.Create with a MapFileConfig
whose Provider is SecretProviderType.Azure, capture the
InvalidOperationException and assert its Message contains the expected text
(e.g., mentions missing VaultUrl or similar) so the test verifies both type and
context; reference the existing act delegate and replace the simple Throw
assertion with one that inspects the exception.Message returned by
SecretProviderFactory.Create.
- Around line 9-70: Add two edge-case unit tests in SecretProviderFactoryTests
to validate defensive behavior: (1) a
Should_ThrowArgumentNullException_When_ConfigIsNull test that calls
SecretProviderFactory.Create(null!) and asserts an ArgumentNullException is
thrown with ParamName "config"; and (2) a
Should_ThrowInvalidOperationException_When_AwsProfileNotFound test that creates
a MapFileConfig with Provider = SecretProviderType.Aws and Profile =
"nonexistent-profile-12345", calls SecretProviderFactory.Create(config) and
asserts an InvalidOperationException is thrown with a message containing the
missing profile string; locate and add these tests alongside the existing tests
referencing SecretProviderFactory.Create, MapFileConfig and SecretProviderType.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 40506a65-b151-4dde-8ec0-adbbb6e666bb

📥 Commits

Reviewing files that changed from the base of the PR and between 7da307f and d139954.

⛔ Files ignored due to path filters (1)
  • e2e/sample/cli-validation.env is excluded by none and included by none
📒 Files selected for processing (19)
  • src/sdks/dotnet/Application/EnvilderClient.cs
  • src/sdks/dotnet/Application/MapFileParser.cs
  • src/sdks/dotnet/Domain/EnvilderOptions.cs
  • src/sdks/dotnet/Domain/ParsedMapFile.cs
  • src/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationProvider.cs
  • src/sdks/dotnet/Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs
  • src/sdks/dotnet/Infrastructure/SecretProviderFactory.cs
  • tests/sdks/dotnet/Application/EnvilderClientTests.cs
  • tests/sdks/dotnet/Application/MapFileParserTests.cs
  • tests/sdks/dotnet/CancellationTokenForTest.cs
  • tests/sdks/dotnet/EndToEnd/ConsumerExperienceTests.cs
  • tests/sdks/dotnet/Envilder.Tests.csproj
  • tests/sdks/dotnet/Fixtures/LocalStackFixture.cs
  • tests/sdks/dotnet/Infrastructure/Aws/AwsSsmAcceptanceTests.cs
  • tests/sdks/dotnet/Infrastructure/Azure/AzureKeyVaultAcceptanceTests.cs
  • tests/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationProviderTests.cs
  • tests/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationSectionBindingTests.cs
  • tests/sdks/dotnet/Infrastructure/DependencyInjection/ServiceCollectionExtensionsTests.cs
  • tests/sdks/dotnet/Infrastructure/SecretProviderFactoryTests.cs
✅ Files skipped from review due to trivial changes (2)
  • tests/sdks/dotnet/CancellationTokenForTest.cs
  • tests/sdks/dotnet/Envilder.Tests.csproj
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/sdks/dotnet/Domain/EnvilderOptions.cs
  • src/sdks/dotnet/Domain/ParsedMapFile.cs
  • tests/sdks/dotnet/Infrastructure/Configuration/EnvilderConfigurationProviderTests.cs
  • src/sdks/dotnet/Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs
  • src/sdks/dotnet/Application/MapFileParser.cs
  • tests/sdks/dotnet/Infrastructure/DependencyInjection/ServiceCollectionExtensionsTests.cs
  • src/sdks/dotnet/Application/EnvilderClient.cs
  • tests/sdks/dotnet/Infrastructure/Azure/AzureKeyVaultAcceptanceTests.cs
  • tests/sdks/dotnet/Infrastructure/Aws/AwsSsmAcceptanceTests.cs
  • tests/sdks/dotnet/Application/EnvilderClientTests.cs
  • tests/sdks/dotnet/Fixtures/LocalStackFixture.cs
  • tests/sdks/dotnet/EndToEnd/ConsumerExperienceTests.cs

Enable slash-delimited keys in map file for hierarchical config
binding (e.g., Database/ConnectionString). Refactor ParsedMapFile
to use immutable properties. Add validation and error handling to
AddEnvilder. Remove unused Region option. Update and add tests for
section binding and error cases.
coderabbitai[bot]

This comment was marked as resolved.

Add explicit null and argument validation to constructors and
methods for improved robustness and fail-fast behavior. Update
tests to cover new error cases. Update package versions and
improve resource handling and code clarity.
coderabbitai[bot]

This comment was marked as resolved.

@macalbert macalbert merged commit 49442a0 into main Apr 8, 2026
5 checks passed
@macalbert macalbert deleted the macalbert/sdk-dotnet branch April 8, 2026 23:17
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review


- name: 🍄 Run tests
if: steps.version-check.outputs.version_changed == 'true'
run: dotnet test ../../../tests/sdks/dotnet/ -c Release --filter "FullyQualifiedName!~Acceptance" --verbosity normal
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 CI test filter does not exclude E2E tests that depend on a developer-specific AWS profile

The CI workflow filter --filter "FullyQualifiedName!~Acceptance" at .github/workflows/publish-nuget.yml:58 excludes AwsSsmAcceptanceTests and AzureKeyVaultAcceptanceTests but does not exclude ConsumerExperienceTests (FQN: Envilder.Tests.EndToEnd.ConsumerExperienceTests) since its name doesn't contain "Acceptance".

Because ConsumerExperienceTests is in the ContainersCollection (tests/sdks/dotnet/Fixtures/ContainersCollection.cs:3-4), xUnit will initialize LocalStackFixture for this collection. During initialization, LocalStackFixture.LoadEnvironmentAsync() reads the embedded secrets-map.json which specifies "profile": "mac", then calls SecretProviderFactory.Create(mapFile.Config) (tests/sdks/dotnet/Fixtures/LocalStackFixture.cs:78). The factory's CreateAwsSecretProvider method at src/sdks/dotnet/Infrastructure/SecretProviderFactory.cs:63-72 throws InvalidOperationException when the "mac" profile is not found in the credential store — which it won't be in CI. This causes all E2E and acceptance tests in the collection to fail.

Prompt for agents
The CI test filter FullyQualifiedName!~Acceptance does not exclude ConsumerExperienceTests (in namespace Envilder.Tests.EndToEnd), which shares the ContainersCollection with the Acceptance tests. This collection triggers LocalStackFixture initialization, which reads secrets-map.json with a hardcoded "profile": "mac" AWS profile, causing SecretProviderFactory.Create to throw in CI.

Two approaches to fix:
1. Update the CI filter to also exclude EndToEnd tests: --filter "FullyQualifiedName!~Acceptance&FullyQualifiedName!~EndToEnd" (or rename ConsumerExperienceTests to include Acceptance in its name/namespace).
2. Make LocalStackFixture.LoadEnvironmentAsync resilient: catch the InvalidOperationException from SecretProviderFactory.Create when the AWS profile is missing and proceed with an empty environment dictionary, since LOCALSTACK_AUTH_TOKEN is only needed for LocalStack Pro features.

Files involved: .github/workflows/publish-nuget.yml (filter), tests/sdks/dotnet/Fixtures/LocalStackFixture.cs (LoadEnvironmentAsync), tests/sdks/dotnet/secrets-map.json (hardcoded profile).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@macalbert macalbert restored the macalbert/sdk-dotnet branch April 8, 2026 23:46
@macalbert macalbert deleted the macalbert/sdk-dotnet branch April 9, 2026 07:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants