svrnty-mcp-gateway/tests/Svrnty.MCP.Gateway.Infrastructure.Tests/Security/ApiKeyAuthProviderTests.cs
Svrnty a4a1dd2e38 docs: comprehensive AI coding assistant research and MCP-first implementation plan
Research conducted on modern AI coding assistants (Cursor, GitHub Copilot, Cline,
Aider, Windsurf, Replit Agent) to understand architecture patterns, context management,
code editing workflows, and tool use protocols.

Key Decision: Pivoted from building full CLI (40-50h) to validation-driven MCP-first
approach (10-15h). Build 5 core CODEX MCP tools that work with ANY coding assistant,
validate adoption over 2-4 weeks, then decide on full CLI if demand proven.

Files:
- research/ai-systems/modern-coding-assistants-architecture.md (comprehensive research)
- research/ai-systems/codex-coding-assistant-implementation-plan.md (original CLI plan, preserved)
- research/ai-systems/codex-mcp-tools-implementation-plan.md (approved MCP-first plan)
- ideas/registry.json (updated with approved MCP tools proposal)

Architech Validation: APPROVED with pivot to MCP-first approach
Human Decision: Approved (pragmatic validation-driven development)

Next: Begin Phase 1 implementation (10-15 hours, 5 core MCP tools)

🤖 Generated with CODEX Research System

Co-Authored-By: The Archivist <archivist@codex.svrnty.io>
Co-Authored-By: The Architech <architech@codex.svrnty.io>
Co-Authored-By: Mathias Beaulieu-Duncan <mat@svrnty.io>
2025-10-22 21:00:34 -04:00

209 lines
5.6 KiB
C#

using Xunit;
using OpenHarbor.MCP.Gateway.Infrastructure.Security;
using OpenHarbor.MCP.Gateway.Core.Models;
namespace OpenHarbor.MCP.Gateway.Infrastructure.Tests.Security;
/// <summary>
/// Unit tests for ApiKeyAuthProvider following TDD approach.
/// Tests API key-based authentication.
/// </summary>
public class ApiKeyAuthProviderTests
{
[Fact]
public async Task AuthenticateAsync_WithValidApiKey_ReturnsSuccess()
{
// Arrange
var validKeys = new Dictionary<string, string>
{
["client-1"] = "secret-key-123",
["client-2"] = "secret-key-456"
};
var provider = new ApiKeyAuthProvider(validKeys);
var context = new AuthenticationContext
{
ClientId = "client-1",
Credentials = "secret-key-123"
};
// Act
var result = await provider.AuthenticateAsync(context);
// Assert
Assert.True(result.IsAuthenticated);
Assert.Equal("client-1", result.ClientId);
Assert.Null(result.ErrorMessage);
}
[Fact]
public async Task AuthenticateAsync_WithInvalidApiKey_ReturnsFailure()
{
// Arrange
var validKeys = new Dictionary<string, string>
{
["client-1"] = "secret-key-123"
};
var provider = new ApiKeyAuthProvider(validKeys);
var context = new AuthenticationContext
{
ClientId = "client-1",
Credentials = "wrong-key"
};
// Act
var result = await provider.AuthenticateAsync(context);
// Assert
Assert.False(result.IsAuthenticated);
Assert.Null(result.ClientId);
Assert.NotNull(result.ErrorMessage);
Assert.Contains("Invalid API key", result.ErrorMessage);
}
[Fact]
public async Task AuthenticateAsync_WithUnknownClient_ReturnsFailure()
{
// Arrange
var validKeys = new Dictionary<string, string>
{
["client-1"] = "secret-key-123"
};
var provider = new ApiKeyAuthProvider(validKeys);
var context = new AuthenticationContext
{
ClientId = "unknown-client",
Credentials = "any-key"
};
// Act
var result = await provider.AuthenticateAsync(context);
// Assert
Assert.False(result.IsAuthenticated);
Assert.Contains("Unknown client", result.ErrorMessage);
}
[Fact]
public async Task AuthenticateAsync_WithNullCredentials_ReturnsFailure()
{
// Arrange
var provider = new ApiKeyAuthProvider(new Dictionary<string, string>
{
["client-1"] = "secret-key-123"
});
var context = new AuthenticationContext
{
ClientId = "client-1",
Credentials = null
};
// Act
var result = await provider.AuthenticateAsync(context);
// Assert
Assert.False(result.IsAuthenticated);
Assert.Contains("credentials", result.ErrorMessage, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task AuthenticateAsync_WithEmptyClientId_ReturnsFailure()
{
// Arrange
var provider = new ApiKeyAuthProvider(new Dictionary<string, string>
{
["client-1"] = "secret-key-123"
});
var context = new AuthenticationContext
{
ClientId = "",
Credentials = "secret-key-123"
};
// Act
var result = await provider.AuthenticateAsync(context);
// Assert
Assert.False(result.IsAuthenticated);
Assert.Contains("Client ID", result.ErrorMessage);
}
[Fact]
public void Constructor_WithNullKeys_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new ApiKeyAuthProvider(null!));
}
[Fact]
public async Task AuthenticateAsync_WithNullContext_ThrowsArgumentNullException()
{
// Arrange
var provider = new ApiKeyAuthProvider(new Dictionary<string, string>());
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(() => provider.AuthenticateAsync(null!));
}
[Fact]
public async Task AuthenticateAsync_CaseSensitiveApiKey_ReturnsFailure()
{
// Arrange
var validKeys = new Dictionary<string, string>
{
["client-1"] = "SecretKey123"
};
var provider = new ApiKeyAuthProvider(validKeys);
var context = new AuthenticationContext
{
ClientId = "client-1",
Credentials = "secretkey123" // Wrong case
};
// Act
var result = await provider.AuthenticateAsync(context);
// Assert
Assert.False(result.IsAuthenticated);
}
[Fact]
public async Task AuthorizeAsync_WithValidClient_ReturnsSuccess()
{
// Arrange
var validKeys = new Dictionary<string, string>
{
["client-1"] = "secret-key-123"
};
var provider = new ApiKeyAuthProvider(validKeys);
var context = new AuthorizationContext
{
ClientId = "client-1",
Resource = "search_codex",
Action = "invoke"
};
// Act
var result = await provider.AuthorizeAsync(context);
// Assert
Assert.True(result.IsAuthorized);
}
[Fact]
public async Task AuthorizeAsync_WithNullContext_ThrowsArgumentNullException()
{
// Arrange
var provider = new ApiKeyAuthProvider(new Dictionary<string, string>());
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(() => provider.AuthorizeAsync(null!));
}
}