mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-09 09:49:58 +00:00
feat: M20 Enhanced Query API — sort, time-range filters, cursor pagination, sparse fields, deployments endpoint
V2 (free) query enhancements for certificates:
- `sort` param with direction (`?sort=-notAfter` for descending)
- Time-range filters: `expires_before`, `expires_after`, `created_after`, `updated_after`
- Cursor-based pagination (`?cursor=token&page_size=100`) alongside page-based
- Sparse field selection (`?fields=id,commonName,status`)
- Additional filters: `agent_id`, `profile_id`
- New endpoint: `GET /api/v1/certificates/{id}/deployments`
25 new tests (12 handler + 13 e2e) covering all M20 features.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,22 +12,25 @@ import (
|
||||
|
||||
"github.com/shankar0123/certctl/internal/api/middleware"
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
"github.com/shankar0123/certctl/internal/repository"
|
||||
)
|
||||
|
||||
// MockCertificateService is a mock implementation of CertificateService interface.
|
||||
type MockCertificateService struct {
|
||||
ListCertificatesFn func(status, environment, ownerID, teamID, issuerID string, page, perPage int) ([]domain.ManagedCertificate, int64, error)
|
||||
GetCertificateFn func(id string) (*domain.ManagedCertificate, error)
|
||||
CreateCertificateFn func(cert domain.ManagedCertificate) (*domain.ManagedCertificate, error)
|
||||
UpdateCertificateFn func(id string, cert domain.ManagedCertificate) (*domain.ManagedCertificate, error)
|
||||
ArchiveCertificateFn func(id string) error
|
||||
GetCertificateVersionsFn func(certID string, page, perPage int) ([]domain.CertificateVersion, int64, error)
|
||||
TriggerRenewalFn func(certID string) error
|
||||
TriggerDeploymentFn func(certID string, targetID string) error
|
||||
RevokeCertificateFn func(certID string, reason string) error
|
||||
GetRevokedCertificatesFn func() ([]*domain.CertificateRevocation, error)
|
||||
GenerateDERCRLFn func(issuerID string) ([]byte, error)
|
||||
GetOCSPResponseFn func(issuerID string, serialHex string) ([]byte, error)
|
||||
ListCertificatesFn func(status, environment, ownerID, teamID, issuerID string, page, perPage int) ([]domain.ManagedCertificate, int64, error)
|
||||
ListCertificatesWithFilterFn func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error)
|
||||
GetCertificateFn func(id string) (*domain.ManagedCertificate, error)
|
||||
CreateCertificateFn func(cert domain.ManagedCertificate) (*domain.ManagedCertificate, error)
|
||||
UpdateCertificateFn func(id string, cert domain.ManagedCertificate) (*domain.ManagedCertificate, error)
|
||||
ArchiveCertificateFn func(id string) error
|
||||
GetCertificateVersionsFn func(certID string, page, perPage int) ([]domain.CertificateVersion, int64, error)
|
||||
TriggerRenewalFn func(certID string) error
|
||||
TriggerDeploymentFn func(certID string, targetID string) error
|
||||
RevokeCertificateFn func(certID string, reason string) error
|
||||
GetRevokedCertificatesFn func() ([]*domain.CertificateRevocation, error)
|
||||
GenerateDERCRLFn func(issuerID string) ([]byte, error)
|
||||
GetOCSPResponseFn func(issuerID string, serialHex string) ([]byte, error)
|
||||
GetCertificateDeploymentsFn func(certID string) ([]domain.DeploymentTarget, error)
|
||||
}
|
||||
|
||||
func (m *MockCertificateService) ListCertificates(status, environment, ownerID, teamID, issuerID string, page, perPage int) ([]domain.ManagedCertificate, int64, error) {
|
||||
@@ -114,6 +117,20 @@ func (m *MockCertificateService) GetOCSPResponse(issuerID string, serialHex stri
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockCertificateService) ListCertificatesWithFilter(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if m.ListCertificatesWithFilterFn != nil {
|
||||
return m.ListCertificatesWithFilterFn(filter)
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (m *MockCertificateService) GetCertificateDeployments(certID string) ([]domain.DeploymentTarget, error) {
|
||||
if m.GetCertificateDeploymentsFn != nil {
|
||||
return m.GetCertificateDeploymentsFn(certID)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Helper function to create context with request ID.
|
||||
func contextWithRequestID() context.Context {
|
||||
return context.WithValue(context.Background(), middleware.RequestIDKey{}, "test-request-id-123")
|
||||
@@ -141,8 +158,8 @@ func TestListCertificates_Success(t *testing.T) {
|
||||
}
|
||||
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesFn: func(status, environment, ownerID, teamID, issuerID string, page, perPage int) ([]domain.ManagedCertificate, int64, error) {
|
||||
if page == 1 && perPage == 50 {
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if filter.Page == 1 && filter.PerPage == 50 {
|
||||
return []domain.ManagedCertificate{cert1, cert2}, 2, nil
|
||||
}
|
||||
return nil, 0, nil
|
||||
@@ -180,8 +197,8 @@ func TestListCertificates_Success(t *testing.T) {
|
||||
// Test ListCertificates - with filters
|
||||
func TestListCertificates_WithFilters(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesFn: func(status, environment, ownerID, teamID, issuerID string, page, perPage int) ([]domain.ManagedCertificate, int64, error) {
|
||||
if status == "Active" && environment == "prod" {
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if filter.Status == "Active" && filter.Environment == "prod" {
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
}
|
||||
return nil, 0, nil
|
||||
@@ -219,7 +236,7 @@ func TestListCertificates_MethodNotAllowed(t *testing.T) {
|
||||
// Test ListCertificates - service error
|
||||
func TestListCertificates_ServiceError(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesFn: func(status, environment, ownerID, teamID, issuerID string, page, perPage int) ([]domain.ManagedCertificate, int64, error) {
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
return nil, 0, ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
@@ -697,9 +714,9 @@ func TestTriggerDeployment_NoTargetID(t *testing.T) {
|
||||
// Test ListCertificates - invalid page parameter
|
||||
func TestListCertificates_InvalidPageParam(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesFn: func(status, environment, ownerID, teamID, issuerID string, page, perPage int) ([]domain.ManagedCertificate, int64, error) {
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
// Should default to page 1
|
||||
if page == 1 {
|
||||
if filter.Page == 1 {
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
}
|
||||
return nil, 0, nil
|
||||
@@ -721,9 +738,9 @@ func TestListCertificates_InvalidPageParam(t *testing.T) {
|
||||
// Test ListCertificates - per_page exceeds max
|
||||
func TestListCertificates_PerPageExceedsMax(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesFn: func(status, environment, ownerID, teamID, issuerID string, page, perPage int) ([]domain.ManagedCertificate, int64, error) {
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
// Should cap perPage at 500
|
||||
if perPage == 50 { // defaults to 50 if > 500
|
||||
if filter.PerPage == 50 { // defaults to 50 if > 500
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
}
|
||||
return nil, 0, nil
|
||||
@@ -1236,3 +1253,388 @@ func TestHandleOCSP_MethodNotAllowed(t *testing.T) {
|
||||
t.Errorf("expected status %d, got %d", http.StatusMethodNotAllowed, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// === M20 Enhanced Query API Tests ===
|
||||
|
||||
// TestListCertificates_SortParam tests sort parameter parsing and passing to service.
|
||||
func TestListCertificates_SortParam(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
// Handler strips the '-' prefix and sets SortDesc = true
|
||||
if filter.Sort != "notAfter" || !filter.SortDesc {
|
||||
t.Errorf("expected sort=notAfter desc=true, got sort=%s desc=%v", filter.Sort, filter.SortDesc)
|
||||
}
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
},
|
||||
}
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates?sort=-notAfter", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListCertificates(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListCertificates_SortParam_Ascending tests sort parameter without '-' prefix (ascending).
|
||||
func TestListCertificates_SortParam_Ascending(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if filter.Sort != "createdAt" || filter.SortDesc {
|
||||
t.Errorf("expected sort=createdAt desc=false, got sort=%s desc=%v", filter.Sort, filter.SortDesc)
|
||||
}
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
},
|
||||
}
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates?sort=createdAt", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListCertificates(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListCertificates_TimeRangeFilters tests time-range filter parsing.
|
||||
func TestListCertificates_TimeRangeFilters(t *testing.T) {
|
||||
before := time.Now().AddDate(0, 0, 90)
|
||||
after := time.Now().AddDate(0, 0, -90)
|
||||
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if filter.ExpiresBefore == nil {
|
||||
t.Error("expected ExpiresBefore to be set")
|
||||
}
|
||||
if filter.ExpiresAfter == nil {
|
||||
t.Error("expected ExpiresAfter to be set")
|
||||
}
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
url := fmt.Sprintf("/api/v1/certificates?expires_before=%s&expires_after=%s",
|
||||
before.Format(time.RFC3339), after.Format(time.RFC3339))
|
||||
req := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListCertificates(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListCertificates_CreatedAfterFilter tests created_after filter parsing.
|
||||
func TestListCertificates_CreatedAfterFilter(t *testing.T) {
|
||||
past := time.Now().AddDate(-1, 0, 0)
|
||||
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if filter.CreatedAfter == nil {
|
||||
t.Error("expected CreatedAfter to be set")
|
||||
}
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
url := fmt.Sprintf("/api/v1/certificates?created_after=%s", past.Format(time.RFC3339))
|
||||
req := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListCertificates(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListCertificates_CursorPagination tests cursor-based pagination response.
|
||||
func TestListCertificates_CursorPagination(t *testing.T) {
|
||||
cert := domain.ManagedCertificate{
|
||||
ID: "mc-cursor-test-1",
|
||||
CommonName: "cursor.example.com",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
return []domain.ManagedCertificate{cert}, 1, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates?cursor=abc123&page_size=10", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListCertificates(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp CursorPagedResponse
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
if resp.NextCursor == "" {
|
||||
t.Error("expected NextCursor to be populated with cursor pagination")
|
||||
}
|
||||
if resp.PageSize != 10 {
|
||||
t.Errorf("expected PageSize=10, got %d", resp.PageSize)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListCertificates_SparseFields tests field filtering in response.
|
||||
func TestListCertificates_SparseFields(t *testing.T) {
|
||||
cert := domain.ManagedCertificate{
|
||||
ID: "mc-sparse-test-1",
|
||||
Name: "Sparse Test Cert",
|
||||
CommonName: "sparse.example.com",
|
||||
Environment: "staging",
|
||||
Status: domain.CertificateStatusActive,
|
||||
}
|
||||
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if len(filter.Fields) != 2 {
|
||||
t.Errorf("expected 2 fields, got %d", len(filter.Fields))
|
||||
}
|
||||
return []domain.ManagedCertificate{cert}, 1, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates?fields=id,common_name", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListCertificates(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp PagedResponse
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
// Response data should have sparse fields applied
|
||||
data, ok := resp.Data.([]interface{})
|
||||
if !ok || len(data) == 0 {
|
||||
t.Fatal("expected data array in response")
|
||||
}
|
||||
|
||||
certMap, ok := data[0].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("expected cert object in response")
|
||||
}
|
||||
|
||||
// Check that requested fields are present
|
||||
if _, ok := certMap["id"]; !ok {
|
||||
t.Error("expected 'id' field in filtered response")
|
||||
}
|
||||
if _, ok := certMap["common_name"]; !ok {
|
||||
t.Error("expected 'common_name' field in filtered response")
|
||||
}
|
||||
}
|
||||
|
||||
// TestListCertificates_ProfileFilter tests profile_id filter.
|
||||
func TestListCertificates_ProfileFilter(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if filter.ProfileID != "prof-standard" {
|
||||
t.Errorf("expected ProfileID=prof-standard, got %s", filter.ProfileID)
|
||||
}
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates?profile_id=prof-standard", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListCertificates(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListCertificates_AgentIDFilter tests agent_id filter.
|
||||
func TestListCertificates_AgentIDFilter(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if filter.AgentID != "agent-prod-001" {
|
||||
t.Errorf("expected AgentID=agent-prod-001, got %s", filter.AgentID)
|
||||
}
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates?agent_id=agent-prod-001", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListCertificates(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListCertificates_CombinedFilters tests multiple filters together.
|
||||
func TestListCertificates_CombinedFilters(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
ListCertificatesWithFilterFn: func(filter *repository.CertificateFilter) ([]domain.ManagedCertificate, int, error) {
|
||||
if filter.Status != "Active" || filter.Environment != "production" || filter.ProfileID != "prof-standard" {
|
||||
t.Error("expected all filters to be set")
|
||||
}
|
||||
return []domain.ManagedCertificate{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates?status=Active&environment=production&profile_id=prof-standard", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListCertificates(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCertificateDeployments_Success tests retrieving deployments for a certificate.
|
||||
func TestGetCertificateDeployments_Success(t *testing.T) {
|
||||
deployments := []domain.DeploymentTarget{
|
||||
{
|
||||
ID: "t-nginx-prod-1",
|
||||
Name: "NGINX Production",
|
||||
Type: "NGINX",
|
||||
Config: json.RawMessage(`{"cert_path": "/etc/nginx/ssl/cert.pem"}`),
|
||||
},
|
||||
{
|
||||
ID: "t-haproxy-prod-1",
|
||||
Name: "HAProxy Production",
|
||||
Type: "HAProxy",
|
||||
Config: json.RawMessage(`{"pem_path": "/etc/haproxy/ssl/cert.pem"}`),
|
||||
},
|
||||
}
|
||||
|
||||
mock := &MockCertificateService{
|
||||
GetCertificateDeploymentsFn: func(certID string) ([]domain.DeploymentTarget, error) {
|
||||
if certID != "mc-prod-001" {
|
||||
return nil, ErrMockNotFound
|
||||
}
|
||||
return deployments, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates/mc-prod-001/deployments", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetCertificateDeployments(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
if data, ok := resp["data"].([]interface{}); !ok || len(data) != 2 {
|
||||
t.Errorf("expected 2 deployments in response")
|
||||
}
|
||||
|
||||
if total, ok := resp["total"].(float64); !ok || int(total) != 2 {
|
||||
t.Errorf("expected total=2, got %v", resp["total"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCertificateDeployments_NotFound tests 404 for nonexistent certificate.
|
||||
func TestGetCertificateDeployments_NotFound(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
GetCertificateDeploymentsFn: func(certID string) ([]domain.DeploymentTarget, error) {
|
||||
return nil, fmt.Errorf("certificate not found")
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates/mc-nonexistent/deployments", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetCertificateDeployments(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("expected 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCertificateDeployments_Empty tests successful response with no deployments.
|
||||
func TestGetCertificateDeployments_Empty(t *testing.T) {
|
||||
mock := &MockCertificateService{
|
||||
GetCertificateDeploymentsFn: func(certID string) ([]domain.DeploymentTarget, error) {
|
||||
if certID == "mc-no-deployments" {
|
||||
return []domain.DeploymentTarget{}, nil
|
||||
}
|
||||
return nil, ErrMockNotFound
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewCertificateHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/certificates/mc-no-deployments/deployments", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetCertificateDeployments(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
if total, ok := resp["total"].(float64); !ok || int(total) != 0 {
|
||||
t.Errorf("expected total=0, got %v", resp["total"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCertificateDeployments_MethodNotAllowed tests 405 for non-GET requests.
|
||||
func TestGetCertificateDeployments_MethodNotAllowed(t *testing.T) {
|
||||
mock := &MockCertificateService{}
|
||||
handler := NewCertificateHandler(mock)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/certificates/mc-prod-001/deployments", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetCertificateDeployments(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("expected 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user