mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 20:21:29 +00:00
7cb453a336
Mechanical reformat. The new 'gofmt drift' CI step (added in
ci-pipeline-cleanup Phase 4, commit 0f205a8) surfaced 111 files
with accumulated gofmt drift across cmd/, internal/, and deploy/test/.
Each file's diff is gofmt-standard: whitespace adjustments, intra-
group import sorting (alphabetical by import path within blank-line-
separated groups), and struct-tag column alignment. No semantic
changes — verified via 'git diff --ignore-all-space' which shows only
the line-position deltas from import reordering.
The gate stays in place after this commit. Going forward it catches
gofmt drift at PR time.
435 lines
13 KiB
Go
435 lines
13 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shankar0123/certctl/internal/domain"
|
|
"github.com/shankar0123/certctl/internal/service"
|
|
)
|
|
|
|
// G-1 red tests: lock in the HTTP surface of /api/v1/renewal-policies before
|
|
// the production code exists. Every subtest here references a symbol that
|
|
// Phase 2b must introduce:
|
|
//
|
|
// - NewRenewalPolicyHandler(svc) (constructor)
|
|
// - RenewalPolicyService (service-layer interface, in this package)
|
|
// - handler.ListRenewalPolicies / GetRenewalPolicy / CreateRenewalPolicy /
|
|
// UpdateRenewalPolicy / DeleteRenewalPolicy
|
|
// - service.ErrRenewalPolicyDuplicateName (pg 23505 → 409 mapping)
|
|
// - service.ErrRenewalPolicyInUse (pg 23503 → 409 mapping)
|
|
|
|
// MockRenewalPolicyService is a mock implementation of RenewalPolicyService.
|
|
type MockRenewalPolicyService struct {
|
|
ListRenewalPoliciesFn func(page, perPage int) ([]domain.RenewalPolicy, int64, error)
|
|
GetRenewalPolicyFn func(id string) (*domain.RenewalPolicy, error)
|
|
CreateRenewalPolicyFn func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
|
|
UpdateRenewalPolicyFn func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error)
|
|
DeleteRenewalPolicyFn func(id string) error
|
|
}
|
|
|
|
func (m *MockRenewalPolicyService) ListRenewalPolicies(_ context.Context, page, perPage int) ([]domain.RenewalPolicy, int64, error) {
|
|
if m.ListRenewalPoliciesFn != nil {
|
|
return m.ListRenewalPoliciesFn(page, perPage)
|
|
}
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (m *MockRenewalPolicyService) GetRenewalPolicy(_ context.Context, id string) (*domain.RenewalPolicy, error) {
|
|
if m.GetRenewalPolicyFn != nil {
|
|
return m.GetRenewalPolicyFn(id)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockRenewalPolicyService) CreateRenewalPolicy(_ context.Context, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) {
|
|
if m.CreateRenewalPolicyFn != nil {
|
|
return m.CreateRenewalPolicyFn(rp)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockRenewalPolicyService) UpdateRenewalPolicy(_ context.Context, id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) {
|
|
if m.UpdateRenewalPolicyFn != nil {
|
|
return m.UpdateRenewalPolicyFn(id, rp)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockRenewalPolicyService) DeleteRenewalPolicy(_ context.Context, id string) error {
|
|
if m.DeleteRenewalPolicyFn != nil {
|
|
return m.DeleteRenewalPolicyFn(id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ----- List -----
|
|
|
|
func TestListRenewalPolicies_Success(t *testing.T) {
|
|
now := time.Now()
|
|
rp1 := domain.RenewalPolicy{
|
|
ID: "rp-default", Name: "Default", RenewalWindowDays: 30,
|
|
MaxRetries: 3, RetryInterval: 3600, AutoRenew: true,
|
|
CreatedAt: now, UpdatedAt: now,
|
|
}
|
|
rp2 := domain.RenewalPolicy{
|
|
ID: "rp-urgent", Name: "Urgent", RenewalWindowDays: 7,
|
|
MaxRetries: 5, RetryInterval: 600, AutoRenew: true,
|
|
CreatedAt: now, UpdatedAt: now,
|
|
}
|
|
|
|
mock := &MockRenewalPolicyService{
|
|
ListRenewalPoliciesFn: func(page, perPage int) ([]domain.RenewalPolicy, int64, error) {
|
|
return []domain.RenewalPolicy{rp1, rp2}, 2, nil
|
|
},
|
|
}
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/renewal-policies", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListRenewalPolicies(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 TestListRenewalPolicies_ServiceError(t *testing.T) {
|
|
mock := &MockRenewalPolicyService{
|
|
ListRenewalPoliciesFn: func(page, perPage int) ([]domain.RenewalPolicy, int64, error) {
|
|
return nil, 0, ErrMockServiceFailed
|
|
},
|
|
}
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/renewal-policies", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListRenewalPolicies(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected status 500, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListRenewalPolicies_MethodNotAllowed(t *testing.T) {
|
|
handler := NewRenewalPolicyHandler(&MockRenewalPolicyService{})
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/renewal-policies", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListRenewalPolicies(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected status 405, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// ----- Get -----
|
|
|
|
func TestGetRenewalPolicy_Success(t *testing.T) {
|
|
now := time.Now()
|
|
mock := &MockRenewalPolicyService{
|
|
GetRenewalPolicyFn: func(id string) (*domain.RenewalPolicy, error) {
|
|
return &domain.RenewalPolicy{
|
|
ID: id, Name: "Default", RenewalWindowDays: 30,
|
|
MaxRetries: 3, RetryInterval: 3600, AutoRenew: true,
|
|
CreatedAt: now, UpdatedAt: now,
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/renewal-policies/rp-default", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.GetRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetRenewalPolicy_NotFound(t *testing.T) {
|
|
mock := &MockRenewalPolicyService{
|
|
GetRenewalPolicyFn: func(id string) (*domain.RenewalPolicy, error) {
|
|
return nil, ErrMockNotFound
|
|
},
|
|
}
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/renewal-policies/nonexistent", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.GetRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected status 404, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// ----- Create -----
|
|
|
|
func TestCreateRenewalPolicy_Success(t *testing.T) {
|
|
now := time.Now()
|
|
mock := &MockRenewalPolicyService{
|
|
CreateRenewalPolicyFn: func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) {
|
|
rp.ID = "rp-new"
|
|
rp.CreatedAt = now
|
|
rp.UpdatedAt = now
|
|
return &rp, nil
|
|
},
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": "New Policy",
|
|
"renewal_window_days": 30,
|
|
"max_retries": 3,
|
|
"retry_interval_seconds": 3600,
|
|
"auto_renew": true,
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/renewal-policies", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
t.Fatalf("expected status 201, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateRenewalPolicy_MissingName(t *testing.T) {
|
|
body := map[string]interface{}{
|
|
"renewal_window_days": 30,
|
|
"max_retries": 3,
|
|
"retry_interval_seconds": 3600,
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewRenewalPolicyHandler(&MockRenewalPolicyService{})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/renewal-policies", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateRenewalPolicy_InvalidJSON(t *testing.T) {
|
|
handler := NewRenewalPolicyHandler(&MockRenewalPolicyService{})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/renewal-policies", bytes.NewReader([]byte("not json")))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateRenewalPolicy_DuplicateName(t *testing.T) {
|
|
// Service bubbles up ErrRenewalPolicyDuplicateName (pg 23505) → handler maps to 409.
|
|
mock := &MockRenewalPolicyService{
|
|
CreateRenewalPolicyFn: func(rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) {
|
|
return nil, service.ErrRenewalPolicyDuplicateName
|
|
},
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": "Duplicate",
|
|
"renewal_window_days": 30,
|
|
"max_retries": 3,
|
|
"retry_interval_seconds": 3600,
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/renewal-policies", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusConflict {
|
|
t.Fatalf("expected status 409 on duplicate name, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateRenewalPolicy_MethodNotAllowed(t *testing.T) {
|
|
handler := NewRenewalPolicyHandler(&MockRenewalPolicyService{})
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/renewal-policies", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected status 405, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// ----- Update -----
|
|
|
|
func TestUpdateRenewalPolicy_Success(t *testing.T) {
|
|
now := time.Now()
|
|
mock := &MockRenewalPolicyService{
|
|
UpdateRenewalPolicyFn: func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) {
|
|
return &domain.RenewalPolicy{
|
|
ID: id, Name: rp.Name, RenewalWindowDays: rp.RenewalWindowDays,
|
|
MaxRetries: rp.MaxRetries, RetryInterval: rp.RetryInterval,
|
|
AutoRenew: rp.AutoRenew,
|
|
CreatedAt: now, UpdatedAt: now,
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": "Updated Policy",
|
|
"renewal_window_days": 45,
|
|
"max_retries": 5,
|
|
"retry_interval_seconds": 1800,
|
|
"auto_renew": true,
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/renewal-policies/rp-default", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.UpdateRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestUpdateRenewalPolicy_NotFound(t *testing.T) {
|
|
mock := &MockRenewalPolicyService{
|
|
UpdateRenewalPolicyFn: func(id string, rp domain.RenewalPolicy) (*domain.RenewalPolicy, error) {
|
|
return nil, ErrMockNotFound
|
|
},
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": "Updated",
|
|
"renewal_window_days": 30,
|
|
"max_retries": 3,
|
|
"retry_interval_seconds": 3600,
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/renewal-policies/rp-missing", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.UpdateRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected status 404, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// ----- Delete -----
|
|
|
|
func TestDeleteRenewalPolicy_Success(t *testing.T) {
|
|
var deletedID string
|
|
mock := &MockRenewalPolicyService{
|
|
DeleteRenewalPolicyFn: func(id string) error {
|
|
deletedID = id
|
|
return nil
|
|
},
|
|
}
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/renewal-policies/rp-default", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusNoContent {
|
|
t.Fatalf("expected status 204, got %d", w.Code)
|
|
}
|
|
if deletedID != "rp-default" {
|
|
t.Errorf("expected deleted ID 'rp-default', got '%s'", deletedID)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRenewalPolicy_NotFound(t *testing.T) {
|
|
mock := &MockRenewalPolicyService{
|
|
DeleteRenewalPolicyFn: func(id string) error {
|
|
return ErrMockNotFound
|
|
},
|
|
}
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/renewal-policies/rp-missing", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected status 404, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRenewalPolicy_InUseConflict(t *testing.T) {
|
|
// Service bubbles up ErrRenewalPolicyInUse (pg 23503 FK-RESTRICT) → handler maps to 409.
|
|
mock := &MockRenewalPolicyService{
|
|
DeleteRenewalPolicyFn: func(id string) error {
|
|
return service.ErrRenewalPolicyInUse
|
|
},
|
|
}
|
|
|
|
handler := NewRenewalPolicyHandler(mock)
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/renewal-policies/rp-active", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusConflict {
|
|
t.Fatalf("expected status 409 on in-use conflict, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteRenewalPolicy_EmptyID(t *testing.T) {
|
|
handler := NewRenewalPolicyHandler(&MockRenewalPolicyService{})
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/renewal-policies/", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteRenewalPolicy(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|