mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 15:51:30 +00:00
Implement M9: test hardening with handler tests, negative paths, CI coverage gates
All 7 handler files now have test coverage: jobs (14 tests), notifications (11), policies (15), issuers (15), targets (14). Negative-path integration tests cover nonexistent resources, invalid payloads, malformed CSR, expired cert lifecycle, and method-not-allowed errors. CI now enforces coverage thresholds (service 60%+, handler 50%+) and includes connector tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
)
|
||||
|
||||
// MockIssuerService is a mock implementation of IssuerService interface.
|
||||
type MockIssuerService struct {
|
||||
ListIssuersFn func(page, perPage int) ([]domain.Issuer, int64, error)
|
||||
GetIssuerFn func(id string) (*domain.Issuer, error)
|
||||
CreateIssuerFn func(issuer domain.Issuer) (*domain.Issuer, error)
|
||||
UpdateIssuerFn func(id string, issuer domain.Issuer) (*domain.Issuer, error)
|
||||
DeleteIssuerFn func(id string) error
|
||||
TestConnectionFn func(id string) error
|
||||
}
|
||||
|
||||
func (m *MockIssuerService) ListIssuers(page, perPage int) ([]domain.Issuer, int64, error) {
|
||||
if m.ListIssuersFn != nil {
|
||||
return m.ListIssuersFn(page, perPage)
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (m *MockIssuerService) GetIssuer(id string) (*domain.Issuer, error) {
|
||||
if m.GetIssuerFn != nil {
|
||||
return m.GetIssuerFn(id)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockIssuerService) CreateIssuer(issuer domain.Issuer) (*domain.Issuer, error) {
|
||||
if m.CreateIssuerFn != nil {
|
||||
return m.CreateIssuerFn(issuer)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockIssuerService) UpdateIssuer(id string, issuer domain.Issuer) (*domain.Issuer, error) {
|
||||
if m.UpdateIssuerFn != nil {
|
||||
return m.UpdateIssuerFn(id, issuer)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockIssuerService) DeleteIssuer(id string) error {
|
||||
if m.DeleteIssuerFn != nil {
|
||||
return m.DeleteIssuerFn(id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockIssuerService) TestConnection(id string) error {
|
||||
if m.TestConnectionFn != nil {
|
||||
return m.TestConnectionFn(id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestListIssuers_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
iss1 := domain.Issuer{
|
||||
ID: "iss-local",
|
||||
Name: "Local CA",
|
||||
Type: "LocalCA",
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
iss2 := domain.Issuer{
|
||||
ID: "iss-acme",
|
||||
Name: "ACME Staging",
|
||||
Type: "ACME",
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
mock := &MockIssuerService{
|
||||
ListIssuersFn: func(page, perPage int) ([]domain.Issuer, int64, error) {
|
||||
return []domain.Issuer{iss1, iss2}, 2, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/issuers", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListIssuers(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 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)
|
||||
}
|
||||
if resp.Total != 2 {
|
||||
t.Errorf("expected total 2, got %d", resp.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListIssuers_Pagination(t *testing.T) {
|
||||
var capturedPage, capturedPerPage int
|
||||
mock := &MockIssuerService{
|
||||
ListIssuersFn: func(page, perPage int) ([]domain.Issuer, int64, error) {
|
||||
capturedPage = page
|
||||
capturedPerPage = perPage
|
||||
return []domain.Issuer{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/issuers?page=2&per_page=10", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListIssuers(w, req)
|
||||
|
||||
if capturedPage != 2 {
|
||||
t.Errorf("expected page 2, got %d", capturedPage)
|
||||
}
|
||||
if capturedPerPage != 10 {
|
||||
t.Errorf("expected per_page 10, got %d", capturedPerPage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListIssuers_ServiceError(t *testing.T) {
|
||||
mock := &MockIssuerService{
|
||||
ListIssuersFn: func(page, perPage int) ([]domain.Issuer, int64, error) {
|
||||
return nil, 0, ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/issuers", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListIssuers(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListIssuers_MethodNotAllowed(t *testing.T) {
|
||||
handler := NewIssuerHandler(&MockIssuerService{})
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/issuers", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListIssuers(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIssuer_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
mock := &MockIssuerService{
|
||||
GetIssuerFn: func(id string) (*domain.Issuer, error) {
|
||||
return &domain.Issuer{
|
||||
ID: id,
|
||||
Name: "Local CA",
|
||||
Type: "LocalCA",
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/issuers/iss-local", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIssuer_NotFound(t *testing.T) {
|
||||
mock := &MockIssuerService{
|
||||
GetIssuerFn: func(id string) (*domain.Issuer, error) {
|
||||
return nil, ErrMockNotFound
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/issuers/nonexistent", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected status 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIssuer_EmptyID(t *testing.T) {
|
||||
handler := NewIssuerHandler(&MockIssuerService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/issuers/", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIssuer_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
mock := &MockIssuerService{
|
||||
CreateIssuerFn: func(issuer domain.Issuer) (*domain.Issuer, error) {
|
||||
issuer.ID = "iss-new"
|
||||
issuer.CreatedAt = now
|
||||
issuer.UpdatedAt = now
|
||||
return &issuer, nil
|
||||
},
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"name": "New Issuer",
|
||||
"type": "LocalCA",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/issuers", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected status 201, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIssuer_MissingName(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"type": "LocalCA",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewIssuerHandler(&MockIssuerService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/issuers", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIssuer_MissingType(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "New Issuer",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewIssuerHandler(&MockIssuerService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/issuers", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIssuer_InvalidJSON(t *testing.T) {
|
||||
handler := NewIssuerHandler(&MockIssuerService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/issuers", bytes.NewReader([]byte("{invalid")))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIssuer_NameTooLong(t *testing.T) {
|
||||
longName := ""
|
||||
for i := 0; i < 256; i++ {
|
||||
longName += "x"
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"name": longName,
|
||||
"type": "LocalCA",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewIssuerHandler(&MockIssuerService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/issuers", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteIssuer_Success(t *testing.T) {
|
||||
var deletedID string
|
||||
mock := &MockIssuerService{
|
||||
DeleteIssuerFn: func(id string) error {
|
||||
deletedID = id
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/issuers/iss-local", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.DeleteIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("expected status 204, got %d", w.Code)
|
||||
}
|
||||
if deletedID != "iss-local" {
|
||||
t.Errorf("expected deleted ID 'iss-local', got '%s'", deletedID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteIssuer_ServiceError(t *testing.T) {
|
||||
mock := &MockIssuerService{
|
||||
DeleteIssuerFn: func(id string) error {
|
||||
return ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/issuers/iss-local", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.DeleteIssuer(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestConnection_Success(t *testing.T) {
|
||||
mock := &MockIssuerService{
|
||||
TestConnectionFn: func(id string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/issuers/iss-local/test", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.TestConnection(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
var resp map[string]string
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if resp["status"] != "connection_successful" {
|
||||
t.Errorf("expected status 'connection_successful', got '%s'", resp["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestConnection_Failure(t *testing.T) {
|
||||
mock := &MockIssuerService{
|
||||
TestConnectionFn: func(id string) error {
|
||||
return ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewIssuerHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/issuers/iss-local/test", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.TestConnection(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestConnection_EmptyID(t *testing.T) {
|
||||
handler := NewIssuerHandler(&MockIssuerService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/issuers//test", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.TestConnection(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
)
|
||||
|
||||
// MockJobService is a mock implementation of JobService interface.
|
||||
type MockJobService struct {
|
||||
ListJobsFn func(status, jobType string, page, perPage int) ([]domain.Job, int64, error)
|
||||
GetJobFn func(id string) (*domain.Job, error)
|
||||
CancelJobFn func(id string) error
|
||||
}
|
||||
|
||||
func (m *MockJobService) ListJobs(status, jobType string, page, perPage int) ([]domain.Job, int64, error) {
|
||||
if m.ListJobsFn != nil {
|
||||
return m.ListJobsFn(status, jobType, page, perPage)
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (m *MockJobService) GetJob(id string) (*domain.Job, error) {
|
||||
if m.GetJobFn != nil {
|
||||
return m.GetJobFn(id)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockJobService) CancelJob(id string) error {
|
||||
if m.CancelJobFn != nil {
|
||||
return m.CancelJobFn(id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestListJobs_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
job1 := domain.Job{
|
||||
ID: "job-001",
|
||||
Type: domain.JobTypeRenewal,
|
||||
CertificateID: "mc-prod-001",
|
||||
Status: domain.JobStatusPending,
|
||||
Attempts: 0,
|
||||
MaxAttempts: 3,
|
||||
ScheduledAt: now,
|
||||
CreatedAt: now,
|
||||
}
|
||||
job2 := domain.Job{
|
||||
ID: "job-002",
|
||||
Type: domain.JobTypeDeployment,
|
||||
CertificateID: "mc-prod-002",
|
||||
Status: domain.JobStatusCompleted,
|
||||
Attempts: 1,
|
||||
MaxAttempts: 3,
|
||||
ScheduledAt: now,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
mock := &MockJobService{
|
||||
ListJobsFn: func(status, jobType string, page, perPage int) ([]domain.Job, int64, error) {
|
||||
return []domain.Job{job1, job2}, 2, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewJobHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListJobs(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 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)
|
||||
}
|
||||
if resp.Total != 2 {
|
||||
t.Errorf("expected total 2, got %d", resp.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListJobs_FilterByStatus(t *testing.T) {
|
||||
var capturedStatus string
|
||||
mock := &MockJobService{
|
||||
ListJobsFn: func(status, jobType string, page, perPage int) ([]domain.Job, int64, error) {
|
||||
capturedStatus = status
|
||||
return []domain.Job{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewJobHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs?status=Pending", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListJobs(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
if capturedStatus != "Pending" {
|
||||
t.Errorf("expected status filter 'Pending', got '%s'", capturedStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListJobs_FilterByType(t *testing.T) {
|
||||
var capturedType string
|
||||
mock := &MockJobService{
|
||||
ListJobsFn: func(status, jobType string, page, perPage int) ([]domain.Job, int64, error) {
|
||||
capturedType = jobType
|
||||
return []domain.Job{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewJobHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs?type=Renewal", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListJobs(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
if capturedType != "Renewal" {
|
||||
t.Errorf("expected type filter 'Renewal', got '%s'", capturedType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListJobs_ServiceError(t *testing.T) {
|
||||
mock := &MockJobService{
|
||||
ListJobsFn: func(status, jobType string, page, perPage int) ([]domain.Job, int64, error) {
|
||||
return nil, 0, ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewJobHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListJobs(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListJobs_MethodNotAllowed(t *testing.T) {
|
||||
handler := NewJobHandler(&MockJobService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListJobs(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListJobs_Pagination(t *testing.T) {
|
||||
var capturedPage, capturedPerPage int
|
||||
mock := &MockJobService{
|
||||
ListJobsFn: func(status, jobType string, page, perPage int) ([]domain.Job, int64, error) {
|
||||
capturedPage = page
|
||||
capturedPerPage = perPage
|
||||
return []domain.Job{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewJobHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs?page=3&per_page=25", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListJobs(w, req)
|
||||
|
||||
if capturedPage != 3 {
|
||||
t.Errorf("expected page 3, got %d", capturedPage)
|
||||
}
|
||||
if capturedPerPage != 25 {
|
||||
t.Errorf("expected per_page 25, got %d", capturedPerPage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetJob_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
mock := &MockJobService{
|
||||
GetJobFn: func(id string) (*domain.Job, error) {
|
||||
return &domain.Job{
|
||||
ID: id,
|
||||
Type: domain.JobTypeRenewal,
|
||||
CertificateID: "mc-prod-001",
|
||||
Status: domain.JobStatusPending,
|
||||
ScheduledAt: now,
|
||||
CreatedAt: now,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewJobHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/job-001", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetJob(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetJob_NotFound(t *testing.T) {
|
||||
mock := &MockJobService{
|
||||
GetJobFn: func(id string) (*domain.Job, error) {
|
||||
return nil, ErrMockNotFound
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewJobHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/nonexistent", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetJob(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected status 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetJob_EmptyID(t *testing.T) {
|
||||
handler := NewJobHandler(&MockJobService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetJob(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelJob_Success(t *testing.T) {
|
||||
var cancelledID string
|
||||
mock := &MockJobService{
|
||||
CancelJobFn: func(id string) error {
|
||||
cancelledID = id
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewJobHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/job-001/cancel", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CancelJob(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
if cancelledID != "job-001" {
|
||||
t.Errorf("expected cancelled ID 'job-001', got '%s'", cancelledID)
|
||||
}
|
||||
|
||||
var resp map[string]string
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if resp["status"] != "job_cancelled" {
|
||||
t.Errorf("expected status 'job_cancelled', got '%s'", resp["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelJob_ServiceError(t *testing.T) {
|
||||
mock := &MockJobService{
|
||||
CancelJobFn: func(id string) error {
|
||||
return ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewJobHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/job-001/cancel", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CancelJob(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelJob_MethodNotAllowed(t *testing.T) {
|
||||
handler := NewJobHandler(&MockJobService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/job-001/cancel", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CancelJob(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelJob_EmptyID(t *testing.T) {
|
||||
handler := NewJobHandler(&MockJobService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs//cancel", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CancelJob(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
)
|
||||
|
||||
// MockNotificationService is a mock implementation of NotificationService interface.
|
||||
type MockNotificationService struct {
|
||||
ListNotificationsFn func(page, perPage int) ([]domain.NotificationEvent, int64, error)
|
||||
GetNotificationFn func(id string) (*domain.NotificationEvent, error)
|
||||
MarkAsReadFn func(id string) error
|
||||
}
|
||||
|
||||
func (m *MockNotificationService) ListNotifications(page, perPage int) ([]domain.NotificationEvent, int64, error) {
|
||||
if m.ListNotificationsFn != nil {
|
||||
return m.ListNotificationsFn(page, perPage)
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (m *MockNotificationService) GetNotification(id string) (*domain.NotificationEvent, error) {
|
||||
if m.GetNotificationFn != nil {
|
||||
return m.GetNotificationFn(id)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockNotificationService) MarkAsRead(id string) error {
|
||||
if m.MarkAsReadFn != nil {
|
||||
return m.MarkAsReadFn(id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestListNotifications_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
certID := "mc-prod-001"
|
||||
n1 := domain.NotificationEvent{
|
||||
ID: "notif-001",
|
||||
Type: domain.NotificationTypeExpirationWarning,
|
||||
CertificateID: &certID,
|
||||
Channel: domain.NotificationChannelEmail,
|
||||
Recipient: "admin@example.com",
|
||||
Message: "Certificate expiring in 30 days",
|
||||
Status: "sent",
|
||||
CreatedAt: now,
|
||||
}
|
||||
n2 := domain.NotificationEvent{
|
||||
ID: "notif-002",
|
||||
Type: domain.NotificationTypeRenewalSuccess,
|
||||
CertificateID: &certID,
|
||||
Channel: domain.NotificationChannelWebhook,
|
||||
Recipient: "https://hooks.example.com/cert",
|
||||
Message: "Certificate renewed successfully",
|
||||
Status: "sent",
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
mock := &MockNotificationService{
|
||||
ListNotificationsFn: func(page, perPage int) ([]domain.NotificationEvent, int64, error) {
|
||||
return []domain.NotificationEvent{n1, n2}, 2, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewNotificationHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/notifications", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListNotifications(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 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)
|
||||
}
|
||||
if resp.Total != 2 {
|
||||
t.Errorf("expected total 2, got %d", resp.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNotifications_Pagination(t *testing.T) {
|
||||
var capturedPage, capturedPerPage int
|
||||
mock := &MockNotificationService{
|
||||
ListNotificationsFn: func(page, perPage int) ([]domain.NotificationEvent, int64, error) {
|
||||
capturedPage = page
|
||||
capturedPerPage = perPage
|
||||
return []domain.NotificationEvent{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewNotificationHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/notifications?page=2&per_page=10", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListNotifications(w, req)
|
||||
|
||||
if capturedPage != 2 {
|
||||
t.Errorf("expected page 2, got %d", capturedPage)
|
||||
}
|
||||
if capturedPerPage != 10 {
|
||||
t.Errorf("expected per_page 10, got %d", capturedPerPage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNotifications_ServiceError(t *testing.T) {
|
||||
mock := &MockNotificationService{
|
||||
ListNotificationsFn: func(page, perPage int) ([]domain.NotificationEvent, int64, error) {
|
||||
return nil, 0, ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewNotificationHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/notifications", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListNotifications(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNotifications_MethodNotAllowed(t *testing.T) {
|
||||
handler := NewNotificationHandler(&MockNotificationService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/notifications", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListNotifications(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNotification_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
certID := "mc-prod-001"
|
||||
mock := &MockNotificationService{
|
||||
GetNotificationFn: func(id string) (*domain.NotificationEvent, error) {
|
||||
return &domain.NotificationEvent{
|
||||
ID: id,
|
||||
Type: domain.NotificationTypeExpirationWarning,
|
||||
CertificateID: &certID,
|
||||
Channel: domain.NotificationChannelEmail,
|
||||
Recipient: "admin@example.com",
|
||||
Message: "Certificate expiring",
|
||||
Status: "sent",
|
||||
CreatedAt: now,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewNotificationHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/notifications/notif-001", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetNotification(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNotification_NotFound(t *testing.T) {
|
||||
mock := &MockNotificationService{
|
||||
GetNotificationFn: func(id string) (*domain.NotificationEvent, error) {
|
||||
return nil, ErrMockNotFound
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewNotificationHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/notifications/nonexistent", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetNotification(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected status 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNotification_EmptyID(t *testing.T) {
|
||||
handler := NewNotificationHandler(&MockNotificationService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/notifications/", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetNotification(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkAsRead_Success(t *testing.T) {
|
||||
var markedID string
|
||||
mock := &MockNotificationService{
|
||||
MarkAsReadFn: func(id string) error {
|
||||
markedID = id
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewNotificationHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/notifications/notif-001/read", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.MarkAsRead(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
if markedID != "notif-001" {
|
||||
t.Errorf("expected marked ID 'notif-001', got '%s'", markedID)
|
||||
}
|
||||
|
||||
var resp map[string]string
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if resp["status"] != "marked_as_read" {
|
||||
t.Errorf("expected status 'marked_as_read', got '%s'", resp["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkAsRead_ServiceError(t *testing.T) {
|
||||
mock := &MockNotificationService{
|
||||
MarkAsReadFn: func(id string) error {
|
||||
return ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewNotificationHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/notifications/notif-001/read", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.MarkAsRead(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkAsRead_MethodNotAllowed(t *testing.T) {
|
||||
handler := NewNotificationHandler(&MockNotificationService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/notifications/notif-001/read", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.MarkAsRead(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkAsRead_EmptyID(t *testing.T) {
|
||||
handler := NewNotificationHandler(&MockNotificationService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/notifications//read", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.MarkAsRead(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
)
|
||||
|
||||
// MockPolicyService is a mock implementation of PolicyService interface.
|
||||
type MockPolicyService struct {
|
||||
ListPoliciesFn func(page, perPage int) ([]domain.PolicyRule, int64, error)
|
||||
GetPolicyFn func(id string) (*domain.PolicyRule, error)
|
||||
CreatePolicyFn func(policy domain.PolicyRule) (*domain.PolicyRule, error)
|
||||
UpdatePolicyFn func(id string, policy domain.PolicyRule) (*domain.PolicyRule, error)
|
||||
DeletePolicyFn func(id string) error
|
||||
ListViolationsFn func(policyID string, page, perPage int) ([]domain.PolicyViolation, int64, error)
|
||||
}
|
||||
|
||||
func (m *MockPolicyService) ListPolicies(page, perPage int) ([]domain.PolicyRule, int64, error) {
|
||||
if m.ListPoliciesFn != nil {
|
||||
return m.ListPoliciesFn(page, perPage)
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (m *MockPolicyService) GetPolicy(id string) (*domain.PolicyRule, error) {
|
||||
if m.GetPolicyFn != nil {
|
||||
return m.GetPolicyFn(id)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockPolicyService) CreatePolicy(policy domain.PolicyRule) (*domain.PolicyRule, error) {
|
||||
if m.CreatePolicyFn != nil {
|
||||
return m.CreatePolicyFn(policy)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockPolicyService) UpdatePolicy(id string, policy domain.PolicyRule) (*domain.PolicyRule, error) {
|
||||
if m.UpdatePolicyFn != nil {
|
||||
return m.UpdatePolicyFn(id, policy)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockPolicyService) DeletePolicy(id string) error {
|
||||
if m.DeletePolicyFn != nil {
|
||||
return m.DeletePolicyFn(id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPolicyService) ListViolations(policyID string, page, perPage int) ([]domain.PolicyViolation, int64, error) {
|
||||
if m.ListViolationsFn != nil {
|
||||
return m.ListViolationsFn(policyID, page, perPage)
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func TestListPolicies_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
p1 := domain.PolicyRule{
|
||||
ID: "pol-001",
|
||||
Name: "Allowed Issuers",
|
||||
Type: domain.PolicyTypeAllowedIssuers,
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
p2 := domain.PolicyRule{
|
||||
ID: "pol-002",
|
||||
Name: "Allowed Domains",
|
||||
Type: domain.PolicyTypeAllowedDomains,
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
mock := &MockPolicyService{
|
||||
ListPoliciesFn: func(page, perPage int) ([]domain.PolicyRule, int64, error) {
|
||||
return []domain.PolicyRule{p1, p2}, 2, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/policies", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListPolicies(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 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)
|
||||
}
|
||||
if resp.Total != 2 {
|
||||
t.Errorf("expected total 2, got %d", resp.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPolicies_ServiceError(t *testing.T) {
|
||||
mock := &MockPolicyService{
|
||||
ListPoliciesFn: func(page, perPage int) ([]domain.PolicyRule, int64, error) {
|
||||
return nil, 0, ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/policies", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListPolicies(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPolicies_MethodNotAllowed(t *testing.T) {
|
||||
handler := NewPolicyHandler(&MockPolicyService{})
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/policies", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListPolicies(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPolicy_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
mock := &MockPolicyService{
|
||||
GetPolicyFn: func(id string) (*domain.PolicyRule, error) {
|
||||
return &domain.PolicyRule{
|
||||
ID: id,
|
||||
Name: "Allowed Issuers",
|
||||
Type: domain.PolicyTypeAllowedIssuers,
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/policies/pol-001", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetPolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPolicy_NotFound(t *testing.T) {
|
||||
mock := &MockPolicyService{
|
||||
GetPolicyFn: func(id string) (*domain.PolicyRule, error) {
|
||||
return nil, ErrMockNotFound
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/policies/nonexistent", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetPolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected status 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePolicy_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
mock := &MockPolicyService{
|
||||
CreatePolicyFn: func(policy domain.PolicyRule) (*domain.PolicyRule, error) {
|
||||
policy.ID = "pol-new"
|
||||
policy.CreatedAt = now
|
||||
policy.UpdatedAt = now
|
||||
return &policy, nil
|
||||
},
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"name": "New Policy",
|
||||
"type": "AllowedIssuers",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/policies", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreatePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected status 201, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePolicy_MissingName(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"type": "AllowedIssuers",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewPolicyHandler(&MockPolicyService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/policies", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreatePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePolicy_MissingType(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "New Policy",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewPolicyHandler(&MockPolicyService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/policies", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreatePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePolicy_InvalidType(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "New Policy",
|
||||
"type": "InvalidType",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewPolicyHandler(&MockPolicyService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/policies", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreatePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePolicy_InvalidJSON(t *testing.T) {
|
||||
handler := NewPolicyHandler(&MockPolicyService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/policies", bytes.NewReader([]byte("not json")))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreatePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePolicy_MethodNotAllowed(t *testing.T) {
|
||||
handler := NewPolicyHandler(&MockPolicyService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/policies", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreatePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePolicy_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
mock := &MockPolicyService{
|
||||
UpdatePolicyFn: func(id string, policy domain.PolicyRule) (*domain.PolicyRule, error) {
|
||||
return &domain.PolicyRule{
|
||||
ID: id,
|
||||
Name: policy.Name,
|
||||
Type: domain.PolicyTypeAllowedIssuers,
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"name": "Updated Policy",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/policies/pol-001", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.UpdatePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePolicy_InvalidType(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "Updated Policy",
|
||||
"type": "InvalidType",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewPolicyHandler(&MockPolicyService{})
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/policies/pol-001", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.UpdatePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeletePolicy_Success(t *testing.T) {
|
||||
var deletedID string
|
||||
mock := &MockPolicyService{
|
||||
DeletePolicyFn: func(id string) error {
|
||||
deletedID = id
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/policies/pol-001", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.DeletePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("expected status 204, got %d", w.Code)
|
||||
}
|
||||
if deletedID != "pol-001" {
|
||||
t.Errorf("expected deleted ID 'pol-001', got '%s'", deletedID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeletePolicy_ServiceError(t *testing.T) {
|
||||
mock := &MockPolicyService{
|
||||
DeletePolicyFn: func(id string) error {
|
||||
return ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/policies/pol-001", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.DeletePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeletePolicy_EmptyID(t *testing.T) {
|
||||
handler := NewPolicyHandler(&MockPolicyService{})
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/policies/", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.DeletePolicy(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListViolations_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
v1 := domain.PolicyViolation{
|
||||
ID: "viol-001",
|
||||
CertificateID: "mc-prod-001",
|
||||
RuleID: "pol-001",
|
||||
Message: "Certificate uses disallowed issuer",
|
||||
Severity: domain.PolicySeverityWarning,
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
var capturedPolicyID string
|
||||
mock := &MockPolicyService{
|
||||
ListViolationsFn: func(policyID string, page, perPage int) ([]domain.PolicyViolation, int64, error) {
|
||||
capturedPolicyID = policyID
|
||||
return []domain.PolicyViolation{v1}, 1, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/policies/pol-001/violations", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListViolations(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
if capturedPolicyID != "pol-001" {
|
||||
t.Errorf("expected policy ID 'pol-001', got '%s'", capturedPolicyID)
|
||||
}
|
||||
|
||||
var resp PagedResponse
|
||||
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
|
||||
t.Fatalf("failed to decode response: %v", err)
|
||||
}
|
||||
if resp.Total != 1 {
|
||||
t.Errorf("expected total 1, got %d", resp.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListViolations_ServiceError(t *testing.T) {
|
||||
mock := &MockPolicyService{
|
||||
ListViolationsFn: func(policyID string, page, perPage int) ([]domain.PolicyViolation, int64, error) {
|
||||
return nil, 0, ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewPolicyHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/policies/pol-001/violations", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListViolations(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListViolations_EmptyPolicyID(t *testing.T) {
|
||||
handler := NewPolicyHandler(&MockPolicyService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/policies//violations", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListViolations(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
)
|
||||
|
||||
// MockTargetService is a mock implementation of TargetService interface.
|
||||
type MockTargetService struct {
|
||||
ListTargetsFn func(page, perPage int) ([]domain.DeploymentTarget, int64, error)
|
||||
GetTargetFn func(id string) (*domain.DeploymentTarget, error)
|
||||
CreateTargetFn func(target domain.DeploymentTarget) (*domain.DeploymentTarget, error)
|
||||
UpdateTargetFn func(id string, target domain.DeploymentTarget) (*domain.DeploymentTarget, error)
|
||||
DeleteTargetFn func(id string) error
|
||||
}
|
||||
|
||||
func (m *MockTargetService) ListTargets(page, perPage int) ([]domain.DeploymentTarget, int64, error) {
|
||||
if m.ListTargetsFn != nil {
|
||||
return m.ListTargetsFn(page, perPage)
|
||||
}
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (m *MockTargetService) GetTarget(id string) (*domain.DeploymentTarget, error) {
|
||||
if m.GetTargetFn != nil {
|
||||
return m.GetTargetFn(id)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockTargetService) CreateTarget(target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
||||
if m.CreateTargetFn != nil {
|
||||
return m.CreateTargetFn(target)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockTargetService) UpdateTarget(id string, target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
||||
if m.UpdateTargetFn != nil {
|
||||
return m.UpdateTargetFn(id, target)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MockTargetService) DeleteTarget(id string) error {
|
||||
if m.DeleteTargetFn != nil {
|
||||
return m.DeleteTargetFn(id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestListTargets_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
t1 := domain.DeploymentTarget{
|
||||
ID: "t-nginx-01",
|
||||
Name: "NGINX Proxy",
|
||||
Type: "nginx",
|
||||
AgentID: "agent-001",
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
t2 := domain.DeploymentTarget{
|
||||
ID: "t-f5-01",
|
||||
Name: "F5 LTM",
|
||||
Type: "f5",
|
||||
AgentID: "agent-002",
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
mock := &MockTargetService{
|
||||
ListTargetsFn: func(page, perPage int) ([]domain.DeploymentTarget, int64, error) {
|
||||
return []domain.DeploymentTarget{t1, t2}, 2, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewTargetHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListTargets(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 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)
|
||||
}
|
||||
if resp.Total != 2 {
|
||||
t.Errorf("expected total 2, got %d", resp.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTargets_Pagination(t *testing.T) {
|
||||
var capturedPage, capturedPerPage int
|
||||
mock := &MockTargetService{
|
||||
ListTargetsFn: func(page, perPage int) ([]domain.DeploymentTarget, int64, error) {
|
||||
capturedPage = page
|
||||
capturedPerPage = perPage
|
||||
return []domain.DeploymentTarget{}, 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewTargetHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets?page=4&per_page=5", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListTargets(w, req)
|
||||
|
||||
if capturedPage != 4 {
|
||||
t.Errorf("expected page 4, got %d", capturedPage)
|
||||
}
|
||||
if capturedPerPage != 5 {
|
||||
t.Errorf("expected per_page 5, got %d", capturedPerPage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTargets_ServiceError(t *testing.T) {
|
||||
mock := &MockTargetService{
|
||||
ListTargetsFn: func(page, perPage int) ([]domain.DeploymentTarget, int64, error) {
|
||||
return nil, 0, ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewTargetHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListTargets(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTargets_MethodNotAllowed(t *testing.T) {
|
||||
handler := NewTargetHandler(&MockTargetService{})
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/targets", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ListTargets(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTarget_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
mock := &MockTargetService{
|
||||
GetTargetFn: func(id string) (*domain.DeploymentTarget, error) {
|
||||
return &domain.DeploymentTarget{
|
||||
ID: id,
|
||||
Name: "NGINX Proxy",
|
||||
Type: "nginx",
|
||||
AgentID: "agent-001",
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewTargetHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets/t-nginx-01", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTarget_NotFound(t *testing.T) {
|
||||
mock := &MockTargetService{
|
||||
GetTargetFn: func(id string) (*domain.DeploymentTarget, error) {
|
||||
return nil, ErrMockNotFound
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewTargetHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets/nonexistent", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected status 404, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTarget_EmptyID(t *testing.T) {
|
||||
handler := NewTargetHandler(&MockTargetService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets/", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.GetTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTarget_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
mock := &MockTargetService{
|
||||
CreateTargetFn: func(target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
||||
target.ID = "t-new"
|
||||
target.CreatedAt = now
|
||||
target.UpdatedAt = now
|
||||
return &target, nil
|
||||
},
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"name": "New Target",
|
||||
"type": "nginx",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewTargetHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("expected status 201, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTarget_MissingName(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"type": "nginx",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewTargetHandler(&MockTargetService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTarget_MissingType(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "New Target",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewTargetHandler(&MockTargetService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTarget_InvalidJSON(t *testing.T) {
|
||||
handler := NewTargetHandler(&MockTargetService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader([]byte("not json")))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTarget_NameTooLong(t *testing.T) {
|
||||
longName := ""
|
||||
for i := 0; i < 256; i++ {
|
||||
longName += "x"
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"name": longName,
|
||||
"type": "nginx",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewTargetHandler(&MockTargetService{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/targets", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTarget_MethodNotAllowed(t *testing.T) {
|
||||
handler := NewTargetHandler(&MockTargetService{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/targets", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.CreateTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status 405, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateTarget_Success(t *testing.T) {
|
||||
now := time.Now()
|
||||
mock := &MockTargetService{
|
||||
UpdateTargetFn: func(id string, target domain.DeploymentTarget) (*domain.DeploymentTarget, error) {
|
||||
return &domain.DeploymentTarget{
|
||||
ID: id,
|
||||
Name: target.Name,
|
||||
Type: "nginx",
|
||||
AgentID: "agent-001",
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
body := map[string]interface{}{
|
||||
"name": "Updated Target",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
handler := NewTargetHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/v1/targets/t-nginx-01", bytes.NewReader(bodyBytes))
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.UpdateTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteTarget_Success(t *testing.T) {
|
||||
var deletedID string
|
||||
mock := &MockTargetService{
|
||||
DeleteTargetFn: func(id string) error {
|
||||
deletedID = id
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewTargetHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/targets/t-nginx-01", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.DeleteTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("expected status 204, got %d", w.Code)
|
||||
}
|
||||
if deletedID != "t-nginx-01" {
|
||||
t.Errorf("expected deleted ID 't-nginx-01', got '%s'", deletedID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteTarget_ServiceError(t *testing.T) {
|
||||
mock := &MockTargetService{
|
||||
DeleteTargetFn: func(id string) error {
|
||||
return ErrMockServiceFailed
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewTargetHandler(mock)
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/targets/t-nginx-01", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.DeleteTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteTarget_EmptyID(t *testing.T) {
|
||||
handler := NewTargetHandler(&MockTargetService{})
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/targets/", nil)
|
||||
req = req.WithContext(contextWithRequestID())
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.DeleteTarget(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/api/handler"
|
||||
"github.com/shankar0123/certctl/internal/api/router"
|
||||
"github.com/shankar0123/certctl/internal/connector/issuer/local"
|
||||
"github.com/shankar0123/certctl/internal/domain"
|
||||
"github.com/shankar0123/certctl/internal/service"
|
||||
)
|
||||
|
||||
// setupTestServer creates a fully-wired test server for negative path testing.
|
||||
func setupTestServer(t *testing.T) (*httptest.Server, *mockCertificateRepository, *mockJobRepository, *mockAgentRepository) {
|
||||
t.Helper()
|
||||
|
||||
certRepo := newMockCertificateRepository()
|
||||
jobRepo := newMockJobRepository()
|
||||
auditRepo := newMockAuditRepository()
|
||||
agentRepo := newMockAgentRepository()
|
||||
targetRepo := newMockTargetRepository()
|
||||
notifRepo := newMockNotificationRepository()
|
||||
policyRepo := newMockPolicyRepository()
|
||||
renewalPolicyRepo := newMockRenewalPolicyRepository()
|
||||
issuerRepo := newMockIssuerRepository()
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
localCA := local.New(nil, logger)
|
||||
|
||||
issuerRegistry := map[string]service.IssuerConnector{
|
||||
"iss-local": service.NewIssuerConnectorAdapter(localCA),
|
||||
}
|
||||
|
||||
auditService := service.NewAuditService(auditRepo)
|
||||
policyService := service.NewPolicyService(policyRepo, auditService)
|
||||
certificateService := service.NewCertificateService(certRepo, policyService, auditService)
|
||||
notificationService := service.NewNotificationService(notifRepo, make(map[string]service.Notifier))
|
||||
renewalService := service.NewRenewalService(certRepo, jobRepo, renewalPolicyRepo, auditService, notificationService, issuerRegistry, "server")
|
||||
deploymentService := service.NewDeploymentService(jobRepo, targetRepo, agentRepo, certRepo, auditService, notificationService)
|
||||
jobService := service.NewJobService(jobRepo, renewalService, deploymentService, logger)
|
||||
agentService := service.NewAgentService(agentRepo, certRepo, jobRepo, targetRepo, auditService, issuerRegistry, renewalService)
|
||||
issuerService := service.NewIssuerService(issuerRepo, auditService)
|
||||
|
||||
certificateHandler := handler.NewCertificateHandler(certificateService)
|
||||
issuerHandler := handler.NewIssuerHandler(issuerService)
|
||||
targetHandler := handler.NewTargetHandler(&mockTargetService{targetRepo: targetRepo, auditService: auditService})
|
||||
agentHandler := handler.NewAgentHandler(agentService)
|
||||
jobHandler := handler.NewJobHandler(jobService)
|
||||
policyHandler := handler.NewPolicyHandler(policyService)
|
||||
teamHandler := handler.NewTeamHandler(&mockTeamService{})
|
||||
ownerHandler := handler.NewOwnerHandler(&mockOwnerService{})
|
||||
auditHandler := handler.NewAuditHandler(auditService)
|
||||
notificationHandler := handler.NewNotificationHandler(notificationService)
|
||||
healthHandler := handler.NewHealthHandler("none")
|
||||
|
||||
r := router.New()
|
||||
r.RegisterHandlers(
|
||||
certificateHandler,
|
||||
issuerHandler,
|
||||
targetHandler,
|
||||
agentHandler,
|
||||
jobHandler,
|
||||
policyHandler,
|
||||
teamHandler,
|
||||
ownerHandler,
|
||||
auditHandler,
|
||||
notificationHandler,
|
||||
healthHandler,
|
||||
)
|
||||
|
||||
server := httptest.NewServer(r)
|
||||
t.Cleanup(func() { server.Close() })
|
||||
|
||||
return server, certRepo, jobRepo, agentRepo
|
||||
}
|
||||
|
||||
// TestNegativePaths exercises error paths and edge cases.
|
||||
func TestNegativePaths(t *testing.T) {
|
||||
server, _, _, _ := setupTestServer(t)
|
||||
|
||||
// ======================
|
||||
// Nonexistent resource lookups
|
||||
// ======================
|
||||
t.Run("GetNonexistentCertificate", func(t *testing.T) {
|
||||
resp, err := http.Get(server.URL + "/api/v1/certificates/mc-does-not-exist")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("expected 404, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetNonexistentAgent", func(t *testing.T) {
|
||||
resp, err := http.Get(server.URL + "/api/v1/agents/agent-ghost")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("expected 404, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetNonexistentJob", func(t *testing.T) {
|
||||
resp, err := http.Get(server.URL + "/api/v1/jobs/job-ghost")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("expected 404, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
// ======================
|
||||
// Invalid request bodies
|
||||
// ======================
|
||||
t.Run("CreateCertificateInvalidJSON", func(t *testing.T) {
|
||||
resp, err := http.Post(server.URL+"/api/v1/certificates", "application/json", bytes.NewReader([]byte("not json")))
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("expected 400, got %d. Body: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CreateCertificateMissingCommonName", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "Test Cert",
|
||||
"environment": "test",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
resp, err := http.Post(server.URL+"/api/v1/certificates", "application/json", bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("expected 400, got %d. Body: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CreatePolicyInvalidType", func(t *testing.T) {
|
||||
body := map[string]interface{}{
|
||||
"name": "Bad Policy",
|
||||
"type": "NonexistentType",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
resp, err := http.Post(server.URL+"/api/v1/policies", "application/json", bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
t.Errorf("expected 400, got %d. Body: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
})
|
||||
|
||||
// ======================
|
||||
// Invalid CSR submission
|
||||
// ======================
|
||||
t.Run("SubmitInvalidCSR", func(t *testing.T) {
|
||||
// First register an agent
|
||||
agentBody := map[string]interface{}{
|
||||
"name": "test-agent",
|
||||
"hostname": "test-host",
|
||||
}
|
||||
agentBytes, _ := json.Marshal(agentBody)
|
||||
|
||||
regResp, err := http.Post(server.URL+"/api/v1/agents/register", "application/json", bytes.NewReader(agentBytes))
|
||||
if err != nil {
|
||||
t.Fatalf("register agent failed: %v", err)
|
||||
}
|
||||
defer regResp.Body.Close()
|
||||
|
||||
if regResp.StatusCode != http.StatusCreated {
|
||||
bodyBytes, _ := io.ReadAll(regResp.Body)
|
||||
t.Fatalf("expected 201, got %d. Body: %s", regResp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
|
||||
var agentResp struct {
|
||||
Agent domain.Agent `json:"agent"`
|
||||
APIKey string `json:"api_key"`
|
||||
}
|
||||
if err := json.NewDecoder(regResp.Body).Decode(&agentResp); err != nil {
|
||||
t.Fatalf("failed to decode agent response: %v", err)
|
||||
}
|
||||
|
||||
// Submit garbage CSR
|
||||
csrBody := map[string]interface{}{
|
||||
"csr_pem": "not a valid CSR",
|
||||
}
|
||||
csrBytes, _ := json.Marshal(csrBody)
|
||||
|
||||
csrResp, err := http.Post(
|
||||
fmt.Sprintf("%s/api/v1/agents/%s/csr", server.URL, agentResp.Agent.ID),
|
||||
"application/json",
|
||||
bytes.NewReader(csrBytes),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("CSR submission failed: %v", err)
|
||||
}
|
||||
defer csrResp.Body.Close()
|
||||
|
||||
// Should reject — either 400 (bad CSR format) or 500 (no cert to sign for)
|
||||
if csrResp.StatusCode == http.StatusOK || csrResp.StatusCode == http.StatusCreated {
|
||||
t.Errorf("expected error status for invalid CSR, got %d", csrResp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
// ======================
|
||||
// Heartbeat for nonexistent agent
|
||||
// ======================
|
||||
t.Run("HeartbeatNonexistentAgent", func(t *testing.T) {
|
||||
heartbeatBody := map[string]interface{}{
|
||||
"status": "healthy",
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(heartbeatBody)
|
||||
|
||||
resp, err := http.Post(
|
||||
server.URL+"/api/v1/agents/agent-nonexistent/heartbeat",
|
||||
"application/json",
|
||||
bytes.NewReader(bodyBytes),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Should fail — agent doesn't exist
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
t.Errorf("expected error status for nonexistent agent heartbeat, got 200")
|
||||
}
|
||||
})
|
||||
|
||||
// ======================
|
||||
// Method not allowed
|
||||
// ======================
|
||||
t.Run("PutToListEndpoint", func(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodPut, server.URL+"/api/v1/certificates", nil)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
t.Errorf("expected error for PUT on list endpoint, got 200")
|
||||
}
|
||||
})
|
||||
|
||||
// ======================
|
||||
// Empty list responses
|
||||
// ======================
|
||||
t.Run("ListEmptyCertificates", func(t *testing.T) {
|
||||
resp, err := http.Get(server.URL + "/api/v1/certificates")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("expected 200 for empty list, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("failed to decode: %v", err)
|
||||
}
|
||||
|
||||
total, ok := result["total"].(float64)
|
||||
if !ok || total != 0 {
|
||||
t.Errorf("expected total 0, got %v", result["total"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ListEmptyJobs", func(t *testing.T) {
|
||||
resp, err := http.Get(server.URL + "/api/v1/jobs")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("expected 200 for empty list, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
// ======================
|
||||
// Trigger renewal on nonexistent cert
|
||||
// ======================
|
||||
t.Run("TriggerRenewalNonexistentCert", func(t *testing.T) {
|
||||
resp, err := http.Post(
|
||||
server.URL+"/api/v1/certificates/mc-ghost/renew",
|
||||
"application/json",
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated {
|
||||
t.Errorf("expected error for renewal of nonexistent cert, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestCertificateLifecycleWithExpiredCert verifies handling of an expired certificate.
|
||||
func TestCertificateLifecycleWithExpiredCert(t *testing.T) {
|
||||
server, certRepo, _, _ := setupTestServer(t)
|
||||
|
||||
// Create an already-expired certificate directly in the repo
|
||||
expiredTime := time.Now().Add(-24 * time.Hour)
|
||||
expiredCert := &domain.ManagedCertificate{
|
||||
ID: "mc-expired-001",
|
||||
Name: "Expired Cert",
|
||||
CommonName: "expired.example.com",
|
||||
Status: domain.CertificateStatusExpired,
|
||||
Environment: "prod",
|
||||
IssuerID: "iss-local",
|
||||
RenewalPolicyID: "rp-default",
|
||||
ExpiresAt: expiredTime,
|
||||
CreatedAt: time.Now().Add(-90 * 24 * time.Hour),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
certRepo.certs[expiredCert.ID] = expiredCert
|
||||
|
||||
// Verify we can retrieve the expired cert
|
||||
t.Run("GetExpiredCert", func(t *testing.T) {
|
||||
resp, err := http.Get(server.URL + "/api/v1/certificates/mc-expired-001")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var cert domain.ManagedCertificate
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cert); err != nil {
|
||||
t.Fatalf("failed to decode: %v", err)
|
||||
}
|
||||
|
||||
if cert.Status != domain.CertificateStatusExpired {
|
||||
t.Errorf("expected status Expired, got %s", cert.Status)
|
||||
}
|
||||
})
|
||||
|
||||
// Trigger renewal on expired cert — should succeed (creating a renewal job)
|
||||
t.Run("TriggerRenewalOnExpiredCert", func(t *testing.T) {
|
||||
resp, err := http.Post(
|
||||
server.URL+"/api/v1/certificates/mc-expired-001/renew",
|
||||
"application/json",
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Renewal should be accepted (creates a job) or return an error
|
||||
// if the service doesn't allow renewal on expired certs
|
||||
t.Logf("Renewal trigger on expired cert returned status: %d", resp.StatusCode)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user