Skip to content

Conversation

@digital88
Copy link
Contributor

@digital88 digital88 commented Nov 29, 2025

What does this PR do?

Introduces new wait strategy for Elastic container. Fixes #1592

The new wait strategy queries the /_cluster/health endpoint and checks that response contains either yellow or green status. Elastic documentation about /_cluster/health endpoint: https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-cluster-health

Why is it important?

Stops Elastic tests failing randomly in CI.

How to test this PR?

Run command dotnet test tests/Testcontainers.Elasticsearch.Tests from the root of the repo.

@coderabbitai
Copy link

coderabbitai bot commented Nov 29, 2025

Summary by CodeRabbit

  • Improvements

    • Elasticsearch container startup now uses HTTP health endpoint validation instead of log pattern matching, resulting in more reliable container readiness detection.
    • Connection string scheme selection improved with unified TLS configuration handling.
  • Documentation

    • Updated Elasticsearch usage examples and configuration guidance.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Replaces log-pattern readiness with an HTTP-based /_cluster/health wait strategy (Basic auth, optional TLS); adds TlsEnabled to configuration; selects connection scheme from that flag; stops auto-attaching the old default wait; and refactors tests to inject container instances via an abstract base fixture.

Changes

Cohort / File(s) Summary
Builder & wait strategy
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs
Build now injects a private HTTP-based WaitUntil when no custom wait strategy exists; Init no longer attaches the previous default log-pattern wait.
Wait strategy internals
src/Testcontainers.Elasticsearch/...
Introduces a private nested WaitUntil that uses an HttpClient to GET /_cluster/health, disables TLS validation when configured, adds Basic Authorization header (Base64), parses JSON and checks status for green or yellow.
Configuration (TLS flag)
src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs
Adds public computed property TlsEnabled that inspects xpack.security.enabled and xpack.security.http.ssl.enabled env vars (case-insensitive) to decide TLS usage.
Container — connection & auth
src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs
GetConnectionString now determines scheme from _configuration.TlsEnabled (HTTPS when true), removing prior inline env-var checks.
Global usings
src/Testcontainers.Elasticsearch/Usings.cs
Removed System.Collections.Generic; added System.Net.Http and System.Text.Json.
Tests — fixture refactor
tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs
Test class converted to abstract; container injected via protected constructor; added DisposeAsyncCore() hook; introduced concrete nested fixtures (ElasticsearchDefaultConfiguration, ElasticsearchAuthConfiguration).
Tests — usings
tests/Testcontainers.Elasticsearch.Tests/Usings.cs
Added global using JetBrains.Annotations.
Minor test change (Grafana)
tests/Testcontainers.Grafana.Tests/GrafanaContainerTest.cs
Renamed local variable basicAuthauthToken and updated Authorization header usage.
Docs
docs/modules/elasticsearch.md
Updated examples to show creating container instances and passing them to a base test class (reflects fixture refactor).

Sequence Diagram(s)

sequenceDiagram
    participant Builder as ElasticsearchBuilder
    participant Container as ElasticsearchContainer
    participant HttpClient as HttpClient
    participant Elasticsearch as Elasticsearch HTTP API

    Builder->>Container: Build() / Init() -> attach WaitUntil(configuration) if no custom wait
    Container->>HttpClient: create HttpClient (handler: allow invalid certs if TLS disabled)
    HttpClient->>Elasticsearch: GET (http|https)://host:port/_cluster/health (Authorization: Basic ...)
    Elasticsearch-->>HttpClient: 200 + JSON { "status":"green"|"yellow" } or other response
    HttpClient->>Container: parse JSON -> ready if status green|yellow, else retry/fail
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Check HttpClientHandler TLS bypass and scope to avoid global impact.
  • Verify Basic auth header formation (credentials, Base64 encoding) and disposal of HttpClient.
  • Validate TlsEnabled logic against env-var permutations and case-insensitivity.
  • Review JSON parsing and error/retry behavior in the wait strategy.
  • Ensure test fixture refactor preserves lifecycle and isolation semantics.

Poem

🐇
I hopped from logs to a health-check stream,
I whispered creds and followed an HTTPS dream,
I watched for yellow then green with delight,
I thumped a carrot when the cluster took flight. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive Most changes align with the HTTP wait strategy objective. However, test infrastructure refactoring (ElasticsearchContainerTest to abstract class with injection) and GrafanaContainerTest variable renaming appear tangential to the core issue. Clarify whether test infrastructure changes are necessary for the wait strategy implementation or should be addressed separately to keep this PR focused.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: introducing an HTTP wait strategy for Elasticsearch, which is the primary focus of the changeset.
Description check ✅ Passed The PR description covers all mandatory sections: 'What does this PR do?' explains the new wait strategy, 'Why is it important?' addresses the flaky CI issue, includes issue reference, and provides testing instructions.
Linked Issues check ✅ Passed The PR successfully implements the proposed remedy for issue #1592 by introducing an HTTP-based wait strategy querying /_cluster/health with yellow/green status checks, directly addressing the flaky Elasticsearch container tests.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86148f7 and 04e32d5.

📒 Files selected for processing (1)
  • tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-08T09:04:00.045Z
Learnt from: HofmeisterAn
Repo: testcontainers/testcontainers-dotnet PR: 1509
File: tests/Testcontainers.Grafana.Tests/GrafanaContainerTest.cs:45-46
Timestamp: 2025-11-08T09:04:00.045Z
Learning: In xUnit.net test methods in the testcontainers-dotnet project, ConfigureAwait(true) is recommended and correct to use, as it ensures proper synchronization context capture for xUnit's test parallelism management. Do not suggest changing ConfigureAwait(true) to ConfigureAwait(false) in test methods.

Applied to files:

  • tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs
🧬 Code graph analysis (1)
tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs (4)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (9)
  • ElasticsearchContainer (59-67)
  • ElasticsearchBuilder (28-32)
  • ElasticsearchBuilder (38-42)
  • ElasticsearchBuilder (52-56)
  • ElasticsearchBuilder (70-81)
  • ElasticsearchBuilder (94-97)
  • ElasticsearchBuilder (100-103)
  • ElasticsearchBuilder (106-109)
  • ElasticsearchBuilder (119-122)
src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs (1)
  • ElasticsearchContainer (13-17)
tests/Testcontainers.MongoDb.Tests/MongoDbContainerTest.cs (9)
  • ValueTask (16-20)
  • ValueTask (22-28)
  • ValueTask (85-88)
  • UsedImplicitly (91-98)
  • UsedImplicitly (100-107)
  • UsedImplicitly (110-117)
  • UsedImplicitly (119-126)
  • UsedImplicitly (128-135)
  • UsedImplicitly (138-145)
tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs (5)
  • ValueTask (15-19)
  • ValueTask (21-27)
  • ValueTask (55-58)
  • UsedImplicitly (61-70)
  • UsedImplicitly (72-81)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: ci (Testcontainers, ubuntu-24.04)
  • GitHub Check: ci (Testcontainers.Elasticsearch, ubuntu-24.04)
  • GitHub Check: ci (Testcontainers.Grafana, ubuntu-24.04)
  • GitHub Check: analyze (csharp)
🔇 Additional comments (3)
tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs (3)

3-10: Abstract base test and constructor injection pattern look solid

Using an abstract ElasticsearchContainerTest with a private constructor that injects ElasticsearchContainer keeps the shared test logic centralized while allowing concrete configurations via nested subclasses. The readonly field and ctor wiring are straightforward and align with the container‑per‑configuration pattern used elsewhere in this repo.


13-17: Async lifetime and disposal implementation correctly forwards to the container

InitializeAsync/DisposeAsync and DisposeAsyncCore form a clean async lifecycle: the container is started once per test class and asynchronously disposed by delegating to _elasticsearchContainer.DisposeAsync(), with ConfigureAwait(false) matching the pattern used in other container tests. The implementation satisfies IAsyncLifetime without introducing extra complexity.

Also applies to: 19-25, 45-48


50-68: Nested default/auth configurations and snippet markers are well‑structured

The nested sealed ElasticsearchDefaultConfiguration and ElasticsearchAuthConfiguration classes cleanly provide two concrete container setups (default and explicit password) while reusing the shared base test (PingReturnsValidResponse). The [UsedImplicitly] attributes help tooling, and the // # --8<-- [start:/end:CreateElasticsearchContainer] markers are correctly paired for documentation extraction.


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.

@netlify
Copy link

netlify bot commented Nov 29, 2025

Deploy Preview for testcontainers-dotnet ready!

Name Link
🔨 Latest commit 04e32d5
🔍 Latest deploy log https://app.netlify.com/projects/testcontainers-dotnet/deploys/692d87ecd4fd1a0008c29304
😎 Deploy Preview https://deploy-preview-1593--testcontainers-dotnet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

@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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (2)

133-155: Consider defensive null handling for credentials.

The Username and Password could potentially be null if the configuration is constructed through certain paths (e.g., IContainerConfiguration or IResourceConfiguration constructors). While Init() sets defaults and Validate() checks Password, a null Username would cause issues at line 138.

         public async Task<bool> UntilAsync(IContainer container)
         {
             using var httpMessageHandler = new HttpClientHandler();
             httpMessageHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;

-            var creds = Convert.ToBase64String(Encoding.UTF8.GetBytes(_configuration.Username + ":" + _configuration.Password));
+            var creds = Convert.ToBase64String(Encoding.UTF8.GetBytes(
+                (_configuration.Username ?? DefaultUsername) + ":" + (_configuration.Password ?? DefaultPassword)));

147-151: Response parsing approach is pragmatic but fragile.

Using string.Contains for JSON parsing works but could produce false positives in edge cases (e.g., if the string appears in a different field). Consider using JSON deserialization for robustness, though the current approach is likely sufficient for the cluster health endpoint's stable response format.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e7aad7 and 20d3bd3.

📒 Files selected for processing (4)
  • src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (2 hunks)
  • src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (1 hunks)
  • src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs (1 hunks)
  • src/Testcontainers.Elasticsearch/Usings.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (1)
src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (5)
  • ElasticsearchConfiguration (12-18)
  • ElasticsearchConfiguration (24-28)
  • ElasticsearchConfiguration (34-38)
  • ElasticsearchConfiguration (44-48)
  • ElasticsearchConfiguration (55-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: ci (Testcontainers, ubuntu-24.04)
  • GitHub Check: ci (Testcontainers.Elasticsearch, ubuntu-24.04)
  • GitHub Check: analyze (csharp)
🔇 Additional comments (4)
src/Testcontainers.Elasticsearch/Usings.cs (1)

4-5: LGTM!

The added global using directives for System.Net and System.Net.Http are required to support the new HTTP-based wait strategy implementation.

src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (1)

71-93: LGTM!

The HttpsEnabled property correctly determines HTTPS availability based on Elasticsearch's security configuration. The logic properly defaults to HTTPS enabled (matching Elasticsearch 8.x defaults) and only disables it when both xpack.security.enabled and xpack.security.http.ssl.enabled are explicitly set to "false".

src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs (1)

29-36: LGTM!

Clean refactor that centralizes HTTPS detection logic. Using _configuration.HttpsEnabled ensures consistency between the connection string scheme and the wait strategy's TLS configuration.

src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (1)

66-79: LGTM!

The refactored Init() correctly captures the configuration state before adding the wait strategy. Using builder.DockerResourceConfiguration ensures the WaitUntil class receives the fully configured credentials and environment settings.

Copy link

@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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (1)

140-169: Handle invalid / unexpected JSON in cluster health response more defensively.

The ForResponseMessageMatching lambda currently assumes successful JSON parsing and a non-null Status:

var content = await m.Content.ReadAsStringAsync();
var response = JsonSerializer.Deserialize<ElasticHealthResponse>(content);
return string.Equals(ElasticHealthResponse.YellowStatus, response.Status, StringComparison.OrdinalIgnoreCase) ||
    string.Equals(ElasticHealthResponse.GreenStatus, response.Status, StringComparison.OrdinalIgnoreCase);

Potential issues:

  • JsonSerializer.Deserialize<ElasticHealthResponse> can return null or throw JsonException if the body is not the expected JSON document (e.g., a proxy error page, misconfigured Elasticsearch response, or a different API version), causing a NullReferenceException or unhandled exception in the wait strategy.
  • Status itself can be null, again leading to a NullReferenceException.

Given the goal of removing flakiness, this wait condition should be resilient to malformed / unexpected bodies and simply return false in those situations instead of throwing.

A more robust implementation could look like:

-                .ForResponseMessageMatching(async (m) =>
-                {
-                    var content = await m.Content.ReadAsStringAsync();
-                    var response = JsonSerializer.Deserialize<ElasticHealthResponse>(content);
-                    return string.Equals(ElasticHealthResponse.YellowStatus, response.Status, StringComparison.OrdinalIgnoreCase) ||
-                        string.Equals(ElasticHealthResponse.GreenStatus, response.Status, StringComparison.OrdinalIgnoreCase);
-                });
+                .ForResponseMessageMatching(async m =>
+                {
+                    var content = await m.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+                    ElasticHealthResponse? response;
+                    try
+                    {
+                        response = JsonSerializer.Deserialize<ElasticHealthResponse>(content);
+                    }
+                    catch (JsonException)
+                    {
+                        return false;
+                    }
+
+                    var status = response?.Status;
+
+                    return string.Equals(ElasticHealthResponse.YellowStatus, status, StringComparison.OrdinalIgnoreCase) ||
+                           string.Equals(ElasticHealthResponse.GreenStatus, status, StringComparison.OrdinalIgnoreCase);
+                });

Optionally, you could also make ElasticHealthResponse.Status non-nullable with a sensible default to further reduce null checks.

The ServerCertificateCustomValidationCallback = (_, _, _, _) => true; is understandable in this testcontainer context, but adding a brief comment to signal “intentionally trusting the self‑signed test certificate” might help future readers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20d3bd3 and 69a8d47.

📒 Files selected for processing (2)
  • src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (2 hunks)
  • src/Testcontainers.Elasticsearch/Usings.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Testcontainers.Elasticsearch/Usings.cs
🧰 Additional context used
🧬 Code graph analysis (1)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (1)
src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (5)
  • ElasticsearchConfiguration (12-18)
  • ElasticsearchConfiguration (24-28)
  • ElasticsearchConfiguration (34-38)
  • ElasticsearchConfiguration (44-48)
  • ElasticsearchConfiguration (55-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: ci (Testcontainers.Elasticsearch, ubuntu-24.04)
  • GitHub Check: ci (Testcontainers, ubuntu-24.04)
  • GitHub Check: analyze (csharp)
🔇 Additional comments (1)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (1)

68-79: Configuration snapshot captured at Init may not reflect later mutations; verify configuration lifecycle and mutation patterns.

The WaitUntil instance is constructed with builder.DockerResourceConfiguration during Init() and stores that configuration reference in _configuration. If ElasticsearchConfiguration follows an immutable pattern where Merge or configuration mutators like WithPassword() create new configuration instances rather than modifying the existing one, then the snapshot captured by WaitUntil will be stale for any configuration changes applied after Init() completes.

This would cause:

  • A custom password set via WithPassword() after initialization to be ignored by the health check, which would still send the default credentials.
  • Any HTTPS-related settings changed after Init() to diverge between the running container and the wait strategy's scheme selection.

Verify the following before determining severity:

  1. Whether ElasticsearchConfiguration is immutable and Merge creates new instances.
  2. Whether WithPassword(), WithEnvironment(), and similar mutators are called after Init() in realistic usage patterns.
  3. Whether the stored _configuration reference is actually used by WaitUntil.UntilAsync() at wait time, or whether the up-to-date configuration is retrieved differently.

Additionally, the JSON parsing in WaitUntil.UntilAsync() (lines 140–169) should be hardened: JsonSerializer.Deserialize<ElasticHealthResponse>() can return null or throw exceptions, and subsequent accesses like response.Status are unguarded. Add null checks and exception handling to prevent wait strategy crashes on malformed responses.

Copy link

@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.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69a8d47 and dafdf65.

📒 Files selected for processing (1)
  • src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-17T17:58:43.958Z
Learnt from: diegosasw
Repo: testcontainers/testcontainers-dotnet PR: 1583
File: src/Testcontainers.KurrentDb/Testcontainers.KurrentDb.csproj:7-7
Timestamp: 2025-11-17T17:58:43.958Z
Learning: In the testcontainers-dotnet repository, JetBrains.Annotations should use version 2023.3.0 to maintain consistency with the main Testcontainers csproj, rather than always using the latest available version.

Applied to files:

  • src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs
🧬 Code graph analysis (1)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (4)
src/Testcontainers.MsSql/MsSqlBuilder.cs (1)
  • WaitUntil (132-151)
src/Testcontainers.MariaDb/MariaDbBuilder.cs (2)
  • WaitUntil (129-150)
  • WaitUntil (137-140)
src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (5)
  • ElasticsearchConfiguration (12-18)
  • ElasticsearchConfiguration (24-28)
  • ElasticsearchConfiguration (34-38)
  • ElasticsearchConfiguration (44-48)
  • ElasticsearchConfiguration (55-60)
src/Testcontainers/Builders/ContainerBuilder.cs (1)
  • IContainer (54-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: ci (Testcontainers, ubuntu-24.04)
  • GitHub Check: ci (Testcontainers.Elasticsearch, ubuntu-24.04)
  • GitHub Check: analyze (csharp)
🔇 Additional comments (2)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (2)

169-176: LGTM!

The ElasticHealthResponse DTO is well-structured with proper JSON attribute and sensible default value.


143-149: I've encountered persistent repository clone failures that prevent me from verifying the HttpsEnabled property directly in the codebase. However, based on the context provided in the review comment:

  1. The code snippet shows _configuration.HttpsEnabled being used on line 145
  2. The review references that this should be a computed property added in the PR
  3. The HttpWaitStrategy configuration appears to be using the property correctly

Since I cannot access the repository to definitively verify the property implementation due to technical constraints, I'll provide a rewritten review comment:


Verify HttpsEnabled property implementation in ElasticsearchConfiguration.

The code uses _configuration.HttpsEnabled to configure TLS for the health check endpoint. Ensure this computed property is correctly implemented and properly reflects the HTTPS configuration state.

Copy link

@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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (1)

162-169: Consider making ElasticHealthResponse immutable.

The Status property has a public setter, but since this is only used for deserialization and comparison, consider using init instead of set to prevent unintended modifications.

         [JsonPropertyName("status")]
-        public string Status { get; set; } = "unknown";
+        public string Status { get; init; } = "unknown";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dafdf65 and c1e02f5.

📒 Files selected for processing (4)
  • src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (2 hunks)
  • src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (1 hunks)
  • src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs (2 hunks)
  • tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-17T17:58:43.958Z
Learnt from: diegosasw
Repo: testcontainers/testcontainers-dotnet PR: 1583
File: src/Testcontainers.KurrentDb/Testcontainers.KurrentDb.csproj:7-7
Timestamp: 2025-11-17T17:58:43.958Z
Learning: In the testcontainers-dotnet repository, JetBrains.Annotations should use version 2023.3.0 to maintain consistency with the main Testcontainers csproj, rather than always using the latest available version.

Applied to files:

  • src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs
🧬 Code graph analysis (1)
tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs (2)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (9)
  • ElasticsearchContainer (59-63)
  • ElasticsearchBuilder (28-32)
  • ElasticsearchBuilder (38-42)
  • ElasticsearchBuilder (52-56)
  • ElasticsearchBuilder (66-78)
  • ElasticsearchBuilder (91-94)
  • ElasticsearchBuilder (97-100)
  • ElasticsearchBuilder (103-106)
  • ElasticsearchBuilder (116-119)
src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs (1)
  • ElasticsearchContainer (13-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: ci (Testcontainers, ubuntu-24.04)
  • GitHub Check: ci (Testcontainers.Elasticsearch, ubuntu-24.04)
  • GitHub Check: analyze (csharp)
🔇 Additional comments (7)
src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs (2)

19-28: LGTM!

Clean public API additions. The HttpsEnabled property appropriately delegates to configuration, and GetCredentials() provides a convenient way to access container credentials for authentication.


40-48: LGTM!

The connection string construction correctly uses HttpsEnabled to determine the scheme. The port constant ElasticsearchHttpsPort (9200) is used for both HTTP and HTTPS, which is correct since Elasticsearch uses the same port for both protocols.

src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (2)

66-78: Past review concern addressed: Credentials now extracted at runtime.

The wait strategy no longer captures configuration at initialization time. Credentials are now obtained from the container instance in UntilAsync, ensuring user customizations via .WithPassword() are properly reflected.


125-160: Well-structured wait strategy implementation.

The implementation correctly:

  • Retrieves credentials at runtime from the container instance
  • Handles TLS based on the container's HttpsEnabled property
  • Uses appropriate fallback for null credentials
  • Bypasses certificate validation for self-signed test certificates (with clear comment)
  • Handles JSON deserialization failures gracefully

The previous null-reference concern has been addressed with ?? new ElasticHealthResponse().

tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs (2)

3-11: LGTM! Good test structure for configuration variants.

The refactoring to an abstract base class with constructor injection enables testing multiple container configurations while sharing test logic. This pattern effectively validates that the new wait strategy works correctly with different credential setups.


42-54: Effective test coverage for credential handling.

The CustomCredentialsConfiguration test class validates that the new wait strategy correctly uses custom credentials—directly addressing the timing concern from the previous review where credentials could be captured before user customization.

src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (1)

75-93: The current logic is correct — the review concern is based on an incorrect assumption about Elasticsearch settings.

According to Elasticsearch documentation, xpack.security.enabled and xpack.security.http.ssl.enabled are independent settings. Disabling security (xpack.security.enabled=false) does not automatically disable TLS; TLS is controlled separately by xpack.security.http.ssl.enabled. This means:

  • A user can have security disabled but TLS enabled
  • A user can have security enabled but TLS disabled
  • The property should only consider HTTPS disabled when the SSL setting is explicitly false

The current implementation correctly requires both settings to be false (or unset) to treat HTTPS as disabled, which matches Elasticsearch's actual behavior. The suggested fix of using OR logic would incorrectly disable HTTPS when security is disabled but TLS is explicitly enabled.

@HofmeisterAn HofmeisterAn changed the title (feat): Improved Elastic container wait strategy fix(Elasticsearch): Use HTTP wait strategy Dec 1, 2025
@HofmeisterAn HofmeisterAn added the bug Something isn't working label Dec 1, 2025
Copy link

@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.

Actionable comments posted: 1

🧹 Nitpick comments (3)
tests/Testcontainers.Grafana.Tests/GrafanaContainerTest.cs (1)

38-42: Consider reverting to the original variable name for clarity.

The variable name authToken is less descriptive than basicAuth for this use case. This holds a Base64-encoded Basic authentication credential (username:password), not a token in the typical sense (bearer, JWT, OAuth). The original name better conveys that this is specifically for HTTP Basic authentication.

Apply this diff to revert to the more accurate naming:

-        var authToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join(":", _username, _password)));
+        var basicAuth = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join(":", _username, _password)));

         using var httpClient = new HttpClient();
         httpClient.BaseAddress = new Uri(_grafanaContainer.GetBaseAddress());
-        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authToken);
+        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", basicAuth);
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (2)

62-66: Consider documenting the magic number or using a more explicit check.

The condition WaitStrategies.Count() > 1 relies on implicit knowledge that there's always exactly one default wait strategy from the base builder. Consider adding a clarifying comment or extracting this to a named method/constant for maintainability.

-        var elasticsearchBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 ? this : WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration)));
+        // The base builder adds one default wait strategy (container running). If the user hasn't added any custom strategies, append the Elasticsearch health check.
+        var hasCustomWaitStrategy = DockerResourceConfiguration.WaitStrategies.Count() > 1;
+        var elasticsearchBuilder = hasCustomWaitStrategy ? this : WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration)));

151-163: Consider using TryGetProperty for cleaner control flow.

Using GetProperty with a catch-all relies on exceptions for expected scenarios (malformed/incomplete JSON during startup). TryGetProperty makes the intent explicit and avoids exception overhead during retries.

             try
             {
-                var status = JsonDocument.Parse(jsonString)
-                    .RootElement
-                    .GetProperty("status")
-                    .GetString();
-
-                return "green".Equals(status, comparisonType) || "yellow".Equals(status, comparisonType);
-            }
-            catch
-            {
-                return false;
+                using var document = JsonDocument.Parse(jsonString);
+                if (document.RootElement.TryGetProperty("status", out var statusElement))
+                {
+                    var status = statusElement.GetString();
+                    return "green".Equals(status, comparisonType) || "yellow".Equals(status, comparisonType);
+                }
             }
+            catch (JsonException)
+            {
+            }
+
+            return false;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1e02f5 and 86148f7.

📒 Files selected for processing (8)
  • docs/modules/elasticsearch.md (1 hunks)
  • src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (3 hunks)
  • src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (1 hunks)
  • src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs (1 hunks)
  • src/Testcontainers.Elasticsearch/Usings.cs (1 hunks)
  • tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs (2 hunks)
  • tests/Testcontainers.Elasticsearch.Tests/Usings.cs (1 hunks)
  • tests/Testcontainers.Grafana.Tests/GrafanaContainerTest.cs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/modules/elasticsearch.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Testcontainers.Elasticsearch/Usings.cs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-17T17:58:43.958Z
Learnt from: diegosasw
Repo: testcontainers/testcontainers-dotnet PR: 1583
File: src/Testcontainers.KurrentDb/Testcontainers.KurrentDb.csproj:7-7
Timestamp: 2025-11-17T17:58:43.958Z
Learning: In the testcontainers-dotnet repository, JetBrains.Annotations should use version 2023.3.0 to maintain consistency with the main Testcontainers csproj, rather than always using the latest available version.

Applied to files:

  • tests/Testcontainers.Elasticsearch.Tests/Usings.cs
📚 Learning: 2025-11-08T09:04:00.045Z
Learnt from: HofmeisterAn
Repo: testcontainers/testcontainers-dotnet PR: 1509
File: tests/Testcontainers.Grafana.Tests/GrafanaContainerTest.cs:45-46
Timestamp: 2025-11-08T09:04:00.045Z
Learning: In xUnit.net test methods in the testcontainers-dotnet project, ConfigureAwait(true) is recommended and correct to use, as it ensures proper synchronization context capture for xUnit's test parallelism management. Do not suggest changing ConfigureAwait(true) to ConfigureAwait(false) in test methods.

Applied to files:

  • tests/Testcontainers.Elasticsearch.Tests/Usings.cs
  • tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs
🧬 Code graph analysis (2)
src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (1)
src/Testcontainers/Configurations/Commons/ComposableDictionary`2.cs (1)
  • TryGetValue (58-58)
src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (2)
src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs (1)
  • ElasticsearchContainer (13-17)
src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (5)
  • ElasticsearchConfiguration (12-18)
  • ElasticsearchConfiguration (24-28)
  • ElasticsearchConfiguration (34-38)
  • ElasticsearchConfiguration (44-48)
  • ElasticsearchConfiguration (55-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: ci (Testcontainers.Grafana, ubuntu-24.04)
  • GitHub Check: ci (Testcontainers, ubuntu-24.04)
  • GitHub Check: analyze (csharp)
🔇 Additional comments (7)
tests/Testcontainers.Elasticsearch.Tests/Usings.cs (1)

6-6: LGTM!

The addition of the JetBrains.Annotations global using supports the [UsedImplicitly] attribute used in the test classes to indicate xUnit test discovery. Based on learnings, this library should use version 2023.3.0 for consistency with the main Testcontainers csproj.

src/Testcontainers.Elasticsearch/ElasticsearchConfiguration.cs (1)

71-93: LGTM!

The TlsEnabled property correctly defaults to true (matching Elasticsearch's default security-enabled behavior) and only returns false when both security flags are explicitly disabled. This centralizes the TLS detection logic that was previously duplicated.

src/Testcontainers.Elasticsearch/ElasticsearchContainer.cs (1)

31-31: LGTM!

Clean refactor that delegates TLS detection to the centralized TlsEnabled property in the configuration class, improving maintainability.

src/Testcontainers.Elasticsearch/ElasticsearchBuilder.cs (1)

166-182: LGTM!

The HTTP-based wait strategy correctly configures TLS, Basic authentication, and the /_cluster/health endpoint check. The certificate validation bypass is appropriate for test containers and aligns with the existing behavior documented in GetConnectionString().

tests/Testcontainers.Elasticsearch.Tests/ElasticsearchContainerTest.cs (3)

3-10: LGTM!

Good refactoring to an abstract base class with constructor injection. This pattern cleanly enables testing multiple container configurations while sharing common test logic.


19-25: LGTM!

The dispose pattern is correctly implemented with DisposeAsyncCore and GC.SuppressFinalize.


50-67: LGTM!

The nested test classes correctly demonstrate both default and custom password configurations. The [UsedImplicitly] attribute properly indicates these classes are discovered by xUnit's test runner. Based on learnings, ConfigureAwait(true) is recommended in xUnit test methods in this project.

Copy link
Collaborator

@HofmeisterAn HofmeisterAn left a comment

Choose a reason for hiding this comment

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

Thanks!

@HofmeisterAn HofmeisterAn merged commit 8aa6520 into testcontainers:develop Dec 1, 2025
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Elastic container tests fail randomly

2 participants