// =============================================================================
// WebhookTestHelper.cs
// Sprint: SPRINT_20251229_019 - Integration E2E Validation
// Description: Utility class for webhook testing operations
// =============================================================================
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace StellaOps.Integration.E2E.Integrations.Helpers;
///
/// Provides utility methods for webhook testing, including signature generation
/// and payload manipulation.
///
public static class WebhookTestHelper
{
#region Signature Generation
///
/// Generates an HMAC-SHA256 signature for a webhook payload.
///
/// The webhook payload.
/// The webhook secret.
/// Optional prefix for the signature (e.g., "sha256=").
/// The generated signature.
public static string GenerateHmacSha256Signature(string payload, string secret, string prefix = "sha256=")
{
var secretBytes = Encoding.UTF8.GetBytes(secret);
var payloadBytes = Encoding.UTF8.GetBytes(payload);
using var hmac = new HMACSHA256(secretBytes);
var hash = hmac.ComputeHash(payloadBytes);
return prefix + Convert.ToHexStringLower(hash);
}
///
/// Generates a GitHub-style webhook signature.
///
public static string GenerateGitHubSignature(string payload, string secret)
{
return GenerateHmacSha256Signature(payload, secret, "sha256=");
}
///
/// Generates a GitLab-style webhook token.
/// GitLab uses X-Gitlab-Token header with the secret directly.
///
public static string GenerateGitLabToken(string secret)
{
return secret;
}
///
/// Generates a Gitea-style webhook signature.
///
public static string GenerateGiteaSignature(string payload, string secret)
{
return GenerateHmacSha256Signature(payload, secret, "sha256=");
}
///
/// Generates a Harbor-style webhook signature.
///
public static string GenerateHarborSignature(string payload, string secret)
{
return GenerateHmacSha256Signature(payload, secret, "sha256=");
}
///
/// Generates a Docker Hub-style webhook signature.
///
public static string GenerateDockerHubSignature(string payload, string secret)
{
return GenerateHmacSha256Signature(payload, secret, "sha256=");
}
#endregion
#region Payload Manipulation
///
/// Modifies a JSON payload by updating a specific field.
///
/// The original JSON payload.
/// Dot-separated path to the field (e.g., "repository.name").
/// The new value to set.
/// The modified payload.
public static string ModifyPayloadField(string payload, string jsonPath, object newValue)
{
var doc = JsonDocument.Parse(payload);
var root = doc.RootElement;
var dict = JsonSerializer.Deserialize>(payload, JsonOptions)
?? throw new InvalidOperationException("Failed to parse payload");
SetNestedValue(dict, jsonPath.Split('.'), newValue);
return JsonSerializer.Serialize(dict, JsonOptions);
}
///
/// Corrupts a payload by modifying its hash/digest field.
///
public static string CorruptPayloadDigest(string payload)
{
// Try common digest field names
var digestFields = new[] { "digest", "image-digest", "sha", "hash" };
foreach (var field in digestFields)
{
if (payload.Contains($"\"{field}\""))
{
// Replace any sha256 digest with a corrupted one
return System.Text.RegularExpressions.Regex.Replace(
payload,
@"sha256:[a-f0-9]{64}",
"sha256:0000000000000000000000000000000000000000000000000000000000000000");
}
}
return payload;
}
///
/// Creates a minimal valid webhook payload for testing.
///
public static string CreateMinimalPayload(string provider, string eventType = "push")
{
return provider.ToLowerInvariant() switch
{
"harbor" => CreateMinimalHarborPayload(),
"dockerhub" => CreateMinimalDockerHubPayload(),
"acr" => CreateMinimalAcrPayload(),
"ecr" => CreateMinimalEcrPayload(),
"gcr" => CreateMinimalGcrPayload(),
"ghcr" => CreateMinimalGhcrPayload(),
"github" => CreateMinimalGitHubPushPayload(),
"gitlab" => CreateMinimalGitLabPushPayload(),
"gitea" => CreateMinimalGiteaPushPayload(),
_ => throw new ArgumentException($"Unknown provider: {provider}")
};
}
#endregion
#region Minimal Payload Generators
private static string CreateMinimalHarborPayload()
{
return JsonSerializer.Serialize(new
{
type = "PUSH_ARTIFACT",
occur_at = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
@operator = "test",
event_data = new
{
resources = new[]
{
new
{
digest = "sha256:test123",
tag = "latest",
resource_url = "harbor.example.com/library/test:latest"
}
},
repository = new
{
name = "test",
repo_full_name = "library/test"
}
}
}, JsonOptions);
}
private static string CreateMinimalDockerHubPayload()
{
return JsonSerializer.Serialize(new
{
push_data = new
{
tag = "latest",
pushed_at = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
},
repository = new
{
repo_name = "stellaops/test",
name = "test",
@namespace = "stellaops"
}
}, JsonOptions);
}
private static string CreateMinimalAcrPayload()
{
return JsonSerializer.Serialize(new
{
id = Guid.NewGuid().ToString(),
action = "push",
target = new
{
repository = "stellaops/test",
tag = "latest",
digest = "sha256:test123"
}
}, JsonOptions);
}
private static string CreateMinimalEcrPayload()
{
return JsonSerializer.Serialize(new Dictionary
{
["version"] = "0",
["id"] = Guid.NewGuid().ToString(),
["detail-type"] = "ECR Image Action",
["source"] = "aws.ecr",
["detail"] = new Dictionary
{
["action-type"] = "PUSH",
["repository-name"] = "stellaops/test",
["image-tag"] = "latest"
}
}, JsonOptions);
}
private static string CreateMinimalGcrPayload()
{
var innerData = new { action = "INSERT", tag = "latest", digest = "sha256:test123" };
var base64Data = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(innerData)));
return JsonSerializer.Serialize(new
{
message = new
{
data = base64Data,
messageId = "gcr-test-123"
}
}, JsonOptions);
}
private static string CreateMinimalGhcrPayload()
{
return JsonSerializer.Serialize(new
{
action = "published",
package = new
{
name = "test-package",
package_type = "container",
package_version = new
{
version = "v1.0.0"
}
},
repository = new
{
full_name = "stellaops/test"
}
}, JsonOptions);
}
private static string CreateMinimalGitHubPushPayload()
{
return JsonSerializer.Serialize(new
{
@ref = "refs/heads/main",
after = "abc123",
repository = new
{
full_name = "stellaops/test",
name = "test",
default_branch = "main"
},
pusher = new { name = "test-user" },
sender = new { login = "test-user", type = "User" },
commits = Array.Empty