mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 19:11: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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user