Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/Serilog.Sinks.Grafana.Loki/HttpClients/BaseLokiHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public abstract class BaseLokiHttpClient : ILokiHttpClient
/// <summary>
/// Regex for Tenant ID validation.
/// </summary>
private static readonly Regex TenantIdValueRegex = new Regex(@"^[a-zA-Z0-9]*$");
private static readonly Regex TenantIdValueRegex = new Regex(@"^(?!.*\.\.)(?!\.$)[a-zA-Z0-9!._*'()\-\u005F]*$");

/// <summary>
/// Initializes a new instance of the <see cref="BaseLokiHttpClient"/> class.
Expand Down Expand Up @@ -91,6 +91,28 @@ public virtual void SetTenant(string? tenant)
headers.Add(TenantHeader, tenant);
}

/// <summary>
/// Sets default headers for the HTTP client.
/// Existing headers with the same key will not be overwritten.
/// </summary>
/// <param name="defaultHeaders">A dictionary of headers to set as default.</param>
public virtual void SetDefaultHeaders(IDictionary<string, string> defaultHeaders)
{
if (defaultHeaders == null)
{
throw new ArgumentNullException(nameof(defaultHeaders), "Default headers cannot be null.");
}

foreach (var header in defaultHeaders)
{
// Check if the header already exists before adding
if (!HttpClient.DefaultRequestHeaders.Contains(header.Key))
{
HttpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
}
}

/// <inheritdoc/>
public virtual void Dispose() => HttpClient.Dispose();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,35 @@ public void AuthorizationHeaderShouldNotBeSetWithoutCredentials()
[Fact]
public void TenantHeaderShouldBeCorrect()
{
var tenantId = "lokitenant";
using var client = new TestLokiHttpClient();

client.SetTenant(tenantId);

var tenantHeaders = client.Client.DefaultRequestHeaders.GetValues("X-Scope-OrgID").ToList();
tenantHeaders.ShouldBeEquivalentTo(new List<string> {"lokitenant"});
// List of test cases with tenant IDs and their expected validity
var validTenantIds = new List<(string TenantId, bool IsValid)>
{
("tenant123", true), // Only alphanumeric characters
("tenant-123", true), // Valid special characters
("tenant..123", false), // Double period ".." is not allowed
(".", false), // Single period is not allowed
("tenant!_*.123'()", true), // All allowed special characters
("tenant-123...", false), // Multiple periods at the end are not allowed
("tenant123456...test", false), // Ends with a period "."
("tenant1234567890!@", false), // "@" is not allowed
};

foreach (var (tenantId, isValid) in validTenantIds)
{
using var client = new TestLokiHttpClient();

if (isValid)
{
client.SetTenant(tenantId);

var tenantHeaders = client.Client.DefaultRequestHeaders.GetValues("X-Scope-OrgID").ToList();
tenantHeaders.ShouldBeEquivalentTo(new List<string> { tenantId });
}
else
{
Should.Throw<ArgumentException>(() => client.SetTenant(tenantId));
}
}
}

[Fact]
Expand All @@ -78,4 +100,24 @@ public void TenantHeaderShouldThrowAnExceptionOnTenantIdAgainstRule()

Should.Throw<ArgumentException>(() => client.SetTenant(tenantId));
}

[Fact]
public void SetDefaultHeadersShouldSetHeaderCorrectly()
{
// Arrange
using var httpClient = new HttpClient();
var client = new TestLokiHttpClient(httpClient);

var headersToSet = new Dictionary<string, string>
{
{ "Custom-Header", "HeaderValue" }
};

// Act
client.SetDefaultHeaders(headersToSet);

// Assert
httpClient.DefaultRequestHeaders.Contains("Custom-Header").ShouldBeTrue();
httpClient.DefaultRequestHeaders.GetValues("Custom-Header").ShouldBe(new[] { "HeaderValue" });
}
}