mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 23:11:32 +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.
431 lines
12 KiB
Go
431 lines
12 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shankar0123/certctl/internal/domain"
|
|
)
|
|
|
|
// MockProfileService is a mock implementation of ProfileService interface.
|
|
type MockProfileService struct {
|
|
ListProfilesFn func(page, perPage int) ([]domain.CertificateProfile, int64, error)
|
|
GetProfileFn func(id string) (*domain.CertificateProfile, error)
|
|
CreateProfileFn func(profile domain.CertificateProfile) (*domain.CertificateProfile, error)
|
|
UpdateProfileFn func(id string, profile domain.CertificateProfile) (*domain.CertificateProfile, error)
|
|
DeleteProfileFn func(id string) error
|
|
}
|
|
|
|
func (m *MockProfileService) ListProfiles(_ context.Context, page, perPage int) ([]domain.CertificateProfile, int64, error) {
|
|
if m.ListProfilesFn != nil {
|
|
return m.ListProfilesFn(page, perPage)
|
|
}
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (m *MockProfileService) GetProfile(_ context.Context, id string) (*domain.CertificateProfile, error) {
|
|
if m.GetProfileFn != nil {
|
|
return m.GetProfileFn(id)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockProfileService) CreateProfile(_ context.Context, profile domain.CertificateProfile) (*domain.CertificateProfile, error) {
|
|
if m.CreateProfileFn != nil {
|
|
return m.CreateProfileFn(profile)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockProfileService) UpdateProfile(_ context.Context, id string, profile domain.CertificateProfile) (*domain.CertificateProfile, error) {
|
|
if m.UpdateProfileFn != nil {
|
|
return m.UpdateProfileFn(id, profile)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockProfileService) DeleteProfile(_ context.Context, id string) error {
|
|
if m.DeleteProfileFn != nil {
|
|
return m.DeleteProfileFn(id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestListProfiles_Success(t *testing.T) {
|
|
now := time.Now()
|
|
prof1 := domain.CertificateProfile{
|
|
ID: "prof-standard-tls",
|
|
Name: "Standard TLS",
|
|
AllowedKeyAlgorithms: []domain.KeyAlgorithmRule{
|
|
{Algorithm: "ECDSA", MinSize: 256},
|
|
{Algorithm: "RSA", MinSize: 2048},
|
|
},
|
|
MaxTTLSeconds: 7776000,
|
|
AllowedEKUs: []string{"serverAuth"},
|
|
Enabled: true,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
prof2 := domain.CertificateProfile{
|
|
ID: "prof-internal-mtls",
|
|
Name: "Internal mTLS",
|
|
AllowedKeyAlgorithms: []domain.KeyAlgorithmRule{
|
|
{Algorithm: "ECDSA", MinSize: 256},
|
|
},
|
|
MaxTTLSeconds: 2592000,
|
|
AllowedEKUs: []string{"serverAuth", "clientAuth"},
|
|
Enabled: true,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
mock := &MockProfileService{
|
|
ListProfilesFn: func(page, perPage int) ([]domain.CertificateProfile, int64, error) {
|
|
return []domain.CertificateProfile{prof1, prof2}, 2, nil
|
|
},
|
|
}
|
|
|
|
handler := NewProfileHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/profiles", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListProfiles(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 TestListProfiles_Pagination(t *testing.T) {
|
|
var capturedPage, capturedPerPage int
|
|
mock := &MockProfileService{
|
|
ListProfilesFn: func(page, perPage int) ([]domain.CertificateProfile, int64, error) {
|
|
capturedPage = page
|
|
capturedPerPage = perPage
|
|
return []domain.CertificateProfile{}, 0, nil
|
|
},
|
|
}
|
|
|
|
handler := NewProfileHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/profiles?page=3&per_page=25", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListProfiles(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 TestListProfiles_ServiceError(t *testing.T) {
|
|
mock := &MockProfileService{
|
|
ListProfilesFn: func(page, perPage int) ([]domain.CertificateProfile, int64, error) {
|
|
return nil, 0, ErrMockServiceFailed
|
|
},
|
|
}
|
|
|
|
handler := NewProfileHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/profiles", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListProfiles(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected status 500, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListProfiles_MethodNotAllowed(t *testing.T) {
|
|
handler := NewProfileHandler(&MockProfileService{})
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/profiles", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.ListProfiles(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected status 405, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetProfile_Success(t *testing.T) {
|
|
now := time.Now()
|
|
mock := &MockProfileService{
|
|
GetProfileFn: func(id string) (*domain.CertificateProfile, error) {
|
|
return &domain.CertificateProfile{
|
|
ID: id,
|
|
Name: "Standard TLS",
|
|
MaxTTLSeconds: 7776000,
|
|
Enabled: true,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}, nil
|
|
},
|
|
}
|
|
|
|
handler := NewProfileHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/profiles/prof-standard-tls", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.GetProfile(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetProfile_NotFound(t *testing.T) {
|
|
mock := &MockProfileService{
|
|
GetProfileFn: func(id string) (*domain.CertificateProfile, error) {
|
|
return nil, ErrMockNotFound
|
|
},
|
|
}
|
|
|
|
handler := NewProfileHandler(mock)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/profiles/nonexistent", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.GetProfile(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Fatalf("expected status 404, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetProfile_EmptyID(t *testing.T) {
|
|
handler := NewProfileHandler(&MockProfileService{})
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/profiles/", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.GetProfile(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateProfile_Success(t *testing.T) {
|
|
now := time.Now()
|
|
mock := &MockProfileService{
|
|
CreateProfileFn: func(profile domain.CertificateProfile) (*domain.CertificateProfile, error) {
|
|
profile.ID = "prof-new"
|
|
profile.CreatedAt = now
|
|
profile.UpdatedAt = now
|
|
return &profile, nil
|
|
},
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": "New Profile",
|
|
"max_ttl_seconds": 86400,
|
|
"allowed_ekus": []string{"serverAuth"},
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewProfileHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateProfile(w, req)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
t.Fatalf("expected status 201, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestCreateProfile_MissingName(t *testing.T) {
|
|
body := map[string]interface{}{
|
|
"max_ttl_seconds": 86400,
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewProfileHandler(&MockProfileService{})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateProfile(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateProfile_NameTooLong(t *testing.T) {
|
|
longName := ""
|
|
for i := 0; i < 256; i++ {
|
|
longName += "x"
|
|
}
|
|
body := map[string]interface{}{
|
|
"name": longName,
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewProfileHandler(&MockProfileService{})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateProfile(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateProfile_InvalidJSON(t *testing.T) {
|
|
handler := NewProfileHandler(&MockProfileService{})
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewReader([]byte("{invalid")))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateProfile(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateProfile_MethodNotAllowed(t *testing.T) {
|
|
handler := NewProfileHandler(&MockProfileService{})
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/profiles", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.CreateProfile(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected status 405, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestUpdateProfile_Success(t *testing.T) {
|
|
now := time.Now()
|
|
mock := &MockProfileService{
|
|
UpdateProfileFn: func(id string, profile domain.CertificateProfile) (*domain.CertificateProfile, error) {
|
|
profile.ID = id
|
|
profile.UpdatedAt = now
|
|
return &profile, nil
|
|
},
|
|
}
|
|
|
|
body := map[string]interface{}{
|
|
"name": "Updated Profile",
|
|
"max_ttl_seconds": 172800,
|
|
}
|
|
bodyBytes, _ := json.Marshal(body)
|
|
|
|
handler := NewProfileHandler(mock)
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/profiles/prof-standard-tls", bytes.NewReader(bodyBytes))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.UpdateProfile(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected status 200, got %d; body: %s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestUpdateProfile_InvalidJSON(t *testing.T) {
|
|
handler := NewProfileHandler(&MockProfileService{})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/profiles/prof-x", bytes.NewReader([]byte("{bad")))
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.UpdateProfile(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteProfile_Success(t *testing.T) {
|
|
var deletedID string
|
|
mock := &MockProfileService{
|
|
DeleteProfileFn: func(id string) error {
|
|
deletedID = id
|
|
return nil
|
|
},
|
|
}
|
|
|
|
handler := NewProfileHandler(mock)
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/profiles/prof-standard-tls", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteProfile(w, req)
|
|
|
|
if w.Code != http.StatusNoContent {
|
|
t.Fatalf("expected status 204, got %d", w.Code)
|
|
}
|
|
if deletedID != "prof-standard-tls" {
|
|
t.Errorf("expected deleted ID 'prof-standard-tls', got '%s'", deletedID)
|
|
}
|
|
}
|
|
|
|
func TestDeleteProfile_ServiceError(t *testing.T) {
|
|
mock := &MockProfileService{
|
|
DeleteProfileFn: func(id string) error {
|
|
return ErrMockServiceFailed
|
|
},
|
|
}
|
|
|
|
handler := NewProfileHandler(mock)
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/profiles/prof-x", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteProfile(w, req)
|
|
|
|
if w.Code != http.StatusInternalServerError {
|
|
t.Fatalf("expected status 500, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteProfile_EmptyID(t *testing.T) {
|
|
handler := NewProfileHandler(&MockProfileService{})
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/profiles/", nil)
|
|
req = req.WithContext(contextWithRequestID())
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteProfile(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected status 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteProfile_MethodNotAllowed(t *testing.T) {
|
|
handler := NewProfileHandler(&MockProfileService{})
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/profiles/prof-x", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
handler.DeleteProfile(w, req)
|
|
|
|
if w.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected status 405, got %d", w.Code)
|
|
}
|
|
}
|