- Implement test to deselect a row checkbox in CertificateList by clicking it a second time. - Add test to close detail dialog via the close button in CertificateList. - Add test to close export dialog via the cancel button in CertificateList. - Add test to show KEY format badge when a .key file is uploaded in CertificateUploadDialog. - Add test to ensure no format badge is shown for unknown file extensions in CertificateUploadDialog.
708 lines
26 KiB
Go
708 lines
26 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/Wikid82/charon/backend/internal/services"
|
|
)
|
|
|
|
// --- Delete UUID path with backup service ---
|
|
|
|
func TestDelete_UUID_WithBackup_Success(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
certUUID := uuid.New().String()
|
|
db.Create(&models.SSLCertificate{UUID: certUUID, Name: "backup-uuid", Provider: "custom", Domains: "backup.test"})
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
mock := &mockBackupService{
|
|
createFunc: func() (string, error) { return "/tmp/backup.tar.gz", nil },
|
|
availableSpaceFunc: func() (int64, error) { return 1024 * 1024 * 1024, nil },
|
|
}
|
|
h := NewCertificateHandler(svc, mock, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+certUUID, http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
func TestDelete_UUID_NotFound(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
nonExistentUUID := uuid.New().String()
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+nonExistentUUID, http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
}
|
|
|
|
func TestDelete_UUID_InUse(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
certUUID := uuid.New().String()
|
|
cert := models.SSLCertificate{UUID: certUUID, Name: "inuse-uuid", Provider: "custom", Domains: "inuse.test"}
|
|
db.Create(&cert)
|
|
db.Create(&models.ProxyHost{UUID: "ph-uuid-inuse", Name: "ph", DomainNames: "inuse.test", ForwardHost: "localhost", ForwardPort: 8080, CertificateID: &cert.ID})
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+certUUID, http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusConflict, w.Code)
|
|
}
|
|
|
|
func TestDelete_UUID_BackupLowSpace(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
certUUID := uuid.New().String()
|
|
db.Create(&models.SSLCertificate{UUID: certUUID, Name: "low-space", Provider: "custom", Domains: "lowspace.test"})
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
mock := &mockBackupService{
|
|
availableSpaceFunc: func() (int64, error) { return 1024, nil }, // 1KB - too low
|
|
}
|
|
h := NewCertificateHandler(svc, mock, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+certUUID, http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusInsufficientStorage, w.Code)
|
|
}
|
|
|
|
func TestDelete_UUID_BackupSpaceCheckError(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
certUUID := uuid.New().String()
|
|
db.Create(&models.SSLCertificate{UUID: certUUID, Name: "space-err", Provider: "custom", Domains: "spaceerr.test"})
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
mock := &mockBackupService{
|
|
availableSpaceFunc: func() (int64, error) { return 0, fmt.Errorf("disk error") },
|
|
createFunc: func() (string, error) { return "/tmp/backup.tar.gz", nil },
|
|
}
|
|
h := NewCertificateHandler(svc, mock, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+certUUID, http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
// Space check error → proceeds with backup → succeeds
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
func TestDelete_UUID_BackupCreateError(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
certUUID := uuid.New().String()
|
|
db.Create(&models.SSLCertificate{UUID: certUUID, Name: "backup-fail", Provider: "custom", Domains: "backupfail.test"})
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
mock := &mockBackupService{
|
|
availableSpaceFunc: func() (int64, error) { return 1024 * 1024 * 1024, nil },
|
|
createFunc: func() (string, error) { return "", fmt.Errorf("backup creation failed") },
|
|
}
|
|
h := NewCertificateHandler(svc, mock, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+certUUID, http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|
|
|
|
// --- Delete UUID with notification service ---
|
|
|
|
func TestDelete_UUID_WithNotification(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}, &models.Setting{}, &models.Notification{}, &models.NotificationProvider{}))
|
|
|
|
certUUID := uuid.New().String()
|
|
db.Create(&models.SSLCertificate{UUID: certUUID, Name: "notify-cert", Provider: "custom", Domains: "notify.test"})
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
notifSvc := services.NewNotificationService(db, nil)
|
|
h := NewCertificateHandler(svc, nil, notifSvc)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+certUUID, http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
// --- Validate handler ---
|
|
|
|
func TestValidate_Success(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
certPEM, _, err := generateSelfSignedCertPEM()
|
|
require.NoError(t, err)
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
part, err := writer.CreateFormFile("certificate_file", "cert.pem")
|
|
require.NoError(t, err)
|
|
_, err = part.Write([]byte(certPEM))
|
|
require.NoError(t, err)
|
|
require.NoError(t, writer.Close())
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.POST("/api/certificates/validate", h.Validate)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/validate", body)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
func TestValidate_InvalidCert(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
part, err := writer.CreateFormFile("certificate_file", "cert.pem")
|
|
require.NoError(t, err)
|
|
_, err = part.Write([]byte("not a certificate"))
|
|
require.NoError(t, err)
|
|
require.NoError(t, writer.Close())
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.POST("/api/certificates/validate", h.Validate)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/validate", body)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(t, w.Body.String(), "unrecognized certificate format")
|
|
}
|
|
|
|
func TestValidate_NoCertFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.POST("/api/certificates/validate", h.Validate)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/validate", http.NoBody)
|
|
req.Header.Set("Content-Type", "multipart/form-data; boundary=boundary")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
func TestValidate_WithKeyAndChain(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
certPEM, keyPEM, err := generateSelfSignedCertPEM()
|
|
require.NoError(t, err)
|
|
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
|
|
certPart, err := writer.CreateFormFile("certificate_file", "cert.pem")
|
|
require.NoError(t, err)
|
|
_, err = certPart.Write([]byte(certPEM))
|
|
require.NoError(t, err)
|
|
|
|
keyPart, err := writer.CreateFormFile("key_file", "key.pem")
|
|
require.NoError(t, err)
|
|
_, err = keyPart.Write([]byte(keyPEM))
|
|
require.NoError(t, err)
|
|
|
|
chainPart, err := writer.CreateFormFile("chain_file", "chain.pem")
|
|
require.NoError(t, err)
|
|
_, err = chainPart.Write([]byte(certPEM)) // self-signed chain
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, writer.Close())
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.POST("/api/certificates/validate", h.Validate)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/validate", body)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
// --- Get handler DB error (non-NotFound) ---
|
|
|
|
func TestGet_DBError(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
// Deliberately don't migrate - any query will fail with "no such table"
|
|
|
|
svc := services.NewCertificateService(t.TempDir(), db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.GET("/api/certificates/:uuid", h.Get)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/certificates/"+uuid.New().String(), http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
// Should be 500 since the table doesn't exist
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|
|
|
|
// --- Export handler: re-auth and service error paths ---
|
|
|
|
func TestExport_IncludeKey_MissingPassword(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}, &models.User{}))
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
h.SetDB(db)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.POST("/api/certificates/:uuid/export", h.Export)
|
|
|
|
body := bytes.NewBufferString(`{"format":"pem","include_key":true}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/"+uuid.New().String()+"/export", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
func TestExport_IncludeKey_NoUserContext(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}, &models.User{}))
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
h.SetDB(db)
|
|
|
|
r := gin.New() // no middleware — "user" key absent
|
|
r.POST("/api/certificates/:uuid/export", h.Export)
|
|
|
|
body := bytes.NewBufferString(`{"format":"pem","include_key":true,"password":"somepass"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/"+uuid.New().String()+"/export", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
func TestExport_IncludeKey_InvalidClaimsType(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}, &models.User{}))
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
h.SetDB(db)
|
|
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) { c.Set("user", "not-a-map"); c.Next() })
|
|
r.POST("/api/certificates/:uuid/export", h.Export)
|
|
|
|
body := bytes.NewBufferString(`{"format":"pem","include_key":true,"password":"somepass"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/"+uuid.New().String()+"/export", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
func TestExport_IncludeKey_UserIDNotInClaims(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}, &models.User{}))
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
h.SetDB(db)
|
|
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) { c.Set("user", map[string]any{}); c.Next() }) // no "id" key
|
|
r.POST("/api/certificates/:uuid/export", h.Export)
|
|
|
|
body := bytes.NewBufferString(`{"format":"pem","include_key":true,"password":"somepass"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/"+uuid.New().String()+"/export", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
func TestExport_IncludeKey_UserNotFoundInDB(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}, &models.User{}))
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
h.SetDB(db)
|
|
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) { c.Set("user", map[string]any{"id": float64(9999)}); c.Next() })
|
|
r.POST("/api/certificates/:uuid/export", h.Export)
|
|
|
|
body := bytes.NewBufferString(`{"format":"pem","include_key":true,"password":"somepass"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/"+uuid.New().String()+"/export", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
func TestExport_IncludeKey_WrongPassword(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}, &models.User{}))
|
|
|
|
u := &models.User{UUID: uuid.New().String(), Email: "export@example.com", Name: "Export User"}
|
|
require.NoError(t, u.SetPassword("correctpass"))
|
|
require.NoError(t, db.Create(u).Error)
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
h.SetDB(db)
|
|
|
|
r := gin.New()
|
|
r.Use(func(c *gin.Context) { c.Set("user", map[string]any{"id": float64(u.ID)}); c.Next() })
|
|
r.POST("/api/certificates/:uuid/export", h.Export)
|
|
|
|
body := bytes.NewBufferString(`{"format":"pem","include_key":true,"password":"wrongpass"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/"+uuid.New().String()+"/export", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
|
}
|
|
|
|
func TestExport_CertNotFound(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.POST("/api/certificates/:uuid/export", h.Export)
|
|
|
|
body := bytes.NewBufferString(`{"format":"pem"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/"+uuid.New().String()+"/export", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
}
|
|
|
|
func TestExport_ServiceError(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
certUUID := uuid.New().String()
|
|
cert := models.SSLCertificate{UUID: certUUID, Name: "test", Domains: "test.example.com", Provider: "custom"}
|
|
require.NoError(t, db.Create(&cert).Error)
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.POST("/api/certificates/:uuid/export", h.Export)
|
|
|
|
body := bytes.NewBufferString(`{"format":"unsupported_xyz"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/certificates/"+certUUID+"/export", body)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|
|
|
|
// --- Delete numeric ID paths ---
|
|
|
|
func TestDelete_NumericID_UsageCheckError(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{})) // no ProxyHost → IsCertificateInUse fails
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/1", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|
|
|
|
func TestDelete_NumericID_LowDiskSpace(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
cert := models.SSLCertificate{UUID: uuid.New().String(), Name: "low-space", Domains: "lowspace.example.com", Provider: "custom"}
|
|
require.NoError(t, db.Create(&cert).Error)
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
backup := &mockBackupService{
|
|
availableSpaceFunc: func() (int64, error) { return 1024, nil }, // < 100 MB
|
|
createFunc: func() (string, error) { return "", nil },
|
|
}
|
|
h := NewCertificateHandler(svc, backup, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert.ID), http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusInsufficientStorage, w.Code)
|
|
}
|
|
|
|
func TestDelete_NumericID_BackupError(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
cert := models.SSLCertificate{UUID: uuid.New().String(), Name: "backup-err", Domains: "backuperr.example.com", Provider: "custom"}
|
|
require.NoError(t, db.Create(&cert).Error)
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
backup := &mockBackupService{
|
|
availableSpaceFunc: func() (int64, error) { return 1 << 30, nil }, // 1 GB — plenty
|
|
createFunc: func() (string, error) { return "", fmt.Errorf("backup create failed") },
|
|
}
|
|
h := NewCertificateHandler(svc, backup, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/certificates/%d", cert.ID), http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|
|
|
|
func TestDelete_NumericID_DeleteError(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.ProxyHost{})) // no SSLCertificate → DeleteCertificateByID fails
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/42", http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|
|
|
|
// --- Delete UUID: internal usage-check error ---
|
|
|
|
func TestDelete_UUID_UsageCheckInternalError(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{})) // no ProxyHost → IsCertificateInUse fails
|
|
|
|
certUUID := uuid.New().String()
|
|
cert := models.SSLCertificate{UUID: certUUID, Name: "uuid-err", Domains: "uuiderr.example.com", Provider: "custom"}
|
|
require.NoError(t, db.Create(&cert).Error)
|
|
|
|
svc := services.NewCertificateService(tmpDir, db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.DELETE("/api/certificates/:uuid", h.Delete)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/certificates/"+certUUID, http.NoBody)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|
|
|
|
// --- sendDeleteNotification: rate limit ---
|
|
|
|
func TestSendDeleteNotification_RateLimit(t *testing.T) {
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.SSLCertificate{}, &models.ProxyHost{}))
|
|
|
|
ns := services.NewNotificationService(db, nil)
|
|
svc := services.NewCertificateService(t.TempDir(), db, nil)
|
|
h := NewCertificateHandler(svc, nil, ns)
|
|
|
|
w := httptest.NewRecorder()
|
|
ctx, _ := gin.CreateTestContext(w)
|
|
ctx.Request = httptest.NewRequest(http.MethodDelete, "/", http.NoBody)
|
|
|
|
certRef := uuid.New().String()
|
|
h.sendDeleteNotification(ctx, certRef) // first call — sets timestamp
|
|
h.sendDeleteNotification(ctx, certRef) // second call — hits rate limit branch
|
|
}
|
|
|
|
// --- Update: empty UUID param (lines 207-209) ---
|
|
|
|
func TestUpdate_EmptyUUID(t *testing.T) {
|
|
svc := services.NewCertificateService(t.TempDir(), nil, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
w := httptest.NewRecorder()
|
|
ctx, _ := gin.CreateTestContext(w)
|
|
ctx.Request = httptest.NewRequest(http.MethodPut, "/api/certificates/", bytes.NewBufferString(`{"name":"test"}`))
|
|
ctx.Request.Header.Set("Content-Type", "application/json")
|
|
// No Params set — c.Param("uuid") returns ""
|
|
h.Update(ctx)
|
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
// --- Update: DB error (non-ErrCertNotFound) → lines 223-225 ---
|
|
|
|
func TestUpdate_DBError(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
// Deliberately no AutoMigrate → ssl_certificates table absent → "no such table" error
|
|
|
|
svc := services.NewCertificateService(t.TempDir(), db, nil)
|
|
h := NewCertificateHandler(svc, nil, nil)
|
|
|
|
r := gin.New()
|
|
r.Use(mockAuthMiddleware())
|
|
r.PUT("/api/certificates/:uuid", h.Update)
|
|
|
|
body, _ := json.Marshal(map[string]string{"name": "new-name"})
|
|
req := httptest.NewRequest(http.MethodPut, "/api/certificates/"+uuid.New().String(), bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
|
}
|