522 lines
18 KiB
Go
522 lines
18 KiB
Go
package caddy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/stretchr/testify/assert"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestManager_ListSnapshots_ReadDirError(t *testing.T) {
|
|
// Use a path that does not exist
|
|
tmp := t.TempDir()
|
|
// create manager with a non-existent subdir
|
|
manager := NewManager(nil, nil, filepath.Join(tmp, "nope"), "", false)
|
|
_, err := manager.listSnapshots()
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestManager_RotateSnapshots_NoOp(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
manager := NewManager(nil, nil, tmp, "", false)
|
|
// No snapshots exist; should be no error
|
|
err := manager.rotateSnapshots(10)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestManager_Rollback_NoSnapshots(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
manager := NewManager(nil, nil, tmp, "", false)
|
|
err := manager.rollback(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "no snapshots available")
|
|
}
|
|
|
|
func TestManager_Rollback_UnmarshalError(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
// Write a non-JSON file with .json extension
|
|
p := filepath.Join(tmp, "config-123.json")
|
|
os.WriteFile(p, []byte("not json"), 0644)
|
|
manager := NewManager(nil, nil, tmp, "", false)
|
|
// Reader error should happen before client.Load
|
|
err := manager.rollback(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unmarshal snapshot")
|
|
}
|
|
|
|
func TestManager_Rollback_LoadSnapshotFail(t *testing.T) {
|
|
// Create a valid JSON file and set client to return error for /load
|
|
tmp := t.TempDir()
|
|
p := filepath.Join(tmp, "config-123.json")
|
|
os.WriteFile(p, []byte(`{"apps":{"http":{}}}`), 0644)
|
|
|
|
// Mock client that returns error on Load
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == http.MethodPost {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer server.Close()
|
|
|
|
badClient := NewClient(server.URL)
|
|
manager := NewManager(badClient, nil, tmp, "", false)
|
|
err := manager.rollback(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "load snapshot")
|
|
}
|
|
|
|
func TestManager_SaveSnapshot_WriteError(t *testing.T) {
|
|
// Create a file at path to use as configDir, so writes fail
|
|
tmp := t.TempDir()
|
|
notDir := filepath.Join(tmp, "file-not-dir")
|
|
os.WriteFile(notDir, []byte("data"), 0644)
|
|
manager := NewManager(nil, nil, notDir, "", false)
|
|
_, err := manager.saveSnapshot(&Config{})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "write snapshot")
|
|
}
|
|
|
|
func TestBackupCaddyfile_MkdirAllFailure(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
originalFile := filepath.Join(tmp, "Caddyfile")
|
|
os.WriteFile(originalFile, []byte("original"), 0644)
|
|
// Create a file where the backup dir should be to cause MkdirAll to fail
|
|
badDir := filepath.Join(tmp, "notadir")
|
|
os.WriteFile(badDir, []byte("data"), 0644)
|
|
|
|
_, err := BackupCaddyfile(originalFile, badDir)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
// Note: Deletion failure for rotateSnapshots is difficult to reliably simulate across environments
|
|
// (tests run as root in CI and local dev containers). If needed, add platform-specific tests.
|
|
|
|
func TestManager_SaveSnapshot_Success(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
manager := NewManager(nil, nil, tmp, "", false)
|
|
path, err := manager.saveSnapshot(&Config{})
|
|
assert.NoError(t, err)
|
|
assert.FileExists(t, path)
|
|
}
|
|
|
|
func TestManager_ApplyConfig_WithSettings(t *testing.T) {
|
|
// Mock Caddy Admin API
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == http.MethodPost {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
if r.URL.Path == "/config/" && r.Method == http.MethodGet {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("{\"apps\":{\"http\":{}}}"))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Create settings for acme email and ssl provider
|
|
db.Create(&models.Setting{Key: "caddy.acme_email", Value: "admin@example.com"})
|
|
db.Create(&models.Setting{Key: "caddy.ssl_provider", Value: "zerossl"})
|
|
|
|
// Setup Manager
|
|
tmpDir := t.TempDir()
|
|
client := NewClient(caddyServer.URL)
|
|
manager := NewManager(client, db, tmpDir, "", false)
|
|
|
|
// Create a host
|
|
host := models.ProxyHost{
|
|
DomainNames: "example.com",
|
|
ForwardHost: "127.0.0.1",
|
|
ForwardPort: 8080,
|
|
}
|
|
db.Create(&host)
|
|
|
|
// Apply Config
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.NoError(t, err)
|
|
|
|
// Verify config was saved to DB
|
|
var caddyConfig models.CaddyConfig
|
|
err = db.First(&caddyConfig).Error
|
|
assert.NoError(t, err)
|
|
assert.True(t, caddyConfig.Success)
|
|
}
|
|
|
|
// Skipping rotate snapshot-on-apply warning test — rotation errors are non-fatal and environment
|
|
// dependent. We cover rotateSnapshots failure separately below.
|
|
|
|
func TestManager_RotateSnapshots_ListDirError(t *testing.T) {
|
|
manager := NewManager(nil, nil, filepath.Join(t.TempDir(), "nope"), "", false)
|
|
err := manager.rotateSnapshots(10)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestManager_RotateSnapshots_DeletesOld(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
// create 5 snapshot files with different timestamps
|
|
for i := 1; i <= 5; i++ {
|
|
name := fmt.Sprintf("config-%d.json", i)
|
|
p := filepath.Join(tmp, name)
|
|
os.WriteFile(p, []byte("{}"), 0644)
|
|
// tweak mod time
|
|
os.Chtimes(p, time.Now().Add(time.Duration(i)*time.Second), time.Now().Add(time.Duration(i)*time.Second))
|
|
}
|
|
|
|
manager := NewManager(nil, nil, tmp, "", false)
|
|
// Keep last 2 snapshots
|
|
err := manager.rotateSnapshots(2)
|
|
assert.NoError(t, err)
|
|
|
|
// Ensure only 2 files remain
|
|
files, _ := os.ReadDir(tmp)
|
|
var cnt int
|
|
for _, f := range files {
|
|
if filepath.Ext(f.Name()) == ".json" {
|
|
cnt++
|
|
}
|
|
}
|
|
assert.Equal(t, 2, cnt)
|
|
}
|
|
|
|
func TestManager_ApplyConfig_RotateSnapshotsWarning(t *testing.T) {
|
|
// Setup DB and Caddy server that accepts load
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == http.MethodPost {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
if r.URL.Path == "/config/" && r.Method == http.MethodGet {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("{" + "\"apps\":{\"http\":{}}}"))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Create a host so GenerateConfig produces a config
|
|
host := models.ProxyHost{DomainNames: "rot.example.com", ForwardHost: "127.0.0.1", ForwardPort: 8080, Enabled: true}
|
|
db.Create(&host)
|
|
|
|
// Create manager with a configDir that is not readable (non-existent subdir)
|
|
tmp := t.TempDir()
|
|
// Create snapshot files: make the oldest a non-empty directory to force delete error;
|
|
// generate 11 snapshots so rotateSnapshots(10) will attempt to delete 1
|
|
d1 := filepath.Join(tmp, "config-1.json")
|
|
os.MkdirAll(d1, 0755)
|
|
os.WriteFile(filepath.Join(d1, "inner"), []byte("x"), 0644) // non-empty
|
|
for i := 2; i <= 11; i++ {
|
|
os.WriteFile(filepath.Join(tmp, fmt.Sprintf("config-%d.json", i)), []byte("{}"), 0644)
|
|
}
|
|
// Set modification times to ensure config-1.json is oldest
|
|
for i := 1; i <= 11; i++ {
|
|
p := filepath.Join(tmp, fmt.Sprintf("config-%d.json", i))
|
|
if i == 1 {
|
|
p = d1
|
|
}
|
|
tmo := time.Now().Add(time.Duration(-i) * time.Minute)
|
|
os.Chtimes(p, tmo, tmo)
|
|
}
|
|
|
|
client := NewClient(caddyServer.URL)
|
|
manager := NewManager(client, db, tmp, "", false)
|
|
|
|
// ApplyConfig should succeed even if rotateSnapshots later returns an error
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestManager_ApplyConfig_LoadFailsAndRollbackFails(t *testing.T) {
|
|
// Mock Caddy admin API which returns error for /load so ApplyConfig fails
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == http.MethodPost {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if r.URL.Path == "/config/" && r.Method == http.MethodGet {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("{" + "\"apps\":{\"http\":{}}}"))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Create a host so GenerateConfig produces a config
|
|
host := models.ProxyHost{DomainNames: "fail.example.com", ForwardHost: "127.0.0.1", ForwardPort: 8080, Enabled: true}
|
|
db.Create(&host)
|
|
|
|
tmp := t.TempDir()
|
|
client := NewClient(server.URL)
|
|
manager := NewManager(client, db, tmp, "", false)
|
|
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "apply failed")
|
|
}
|
|
|
|
func TestManager_ApplyConfig_SaveSnapshotFails(t *testing.T) {
|
|
// Setup DB and Caddy server that accepts load
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == http.MethodPost {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
if r.URL.Path == "/config/" && r.Method == http.MethodGet {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("{" + "\"apps\":{\"http\":{}}}"))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name()+"savefail")
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Create a host so GenerateConfig produces a config
|
|
host := models.ProxyHost{DomainNames: "savefail.example.com", ForwardHost: "127.0.0.1", ForwardPort: 8080, Enabled: true}
|
|
db.Create(&host)
|
|
|
|
// Create a file where configDir should be to cause saveSnapshot to fail
|
|
tmp := t.TempDir()
|
|
filePath := filepath.Join(tmp, "file-not-dir")
|
|
os.WriteFile(filePath, []byte("data"), 0644)
|
|
|
|
client := NewClient(caddyServer.URL)
|
|
manager := NewManager(client, db, filePath, "", false)
|
|
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "save snapshot")
|
|
}
|
|
|
|
func TestManager_ApplyConfig_LoadFailsThenRollbackSucceeds(t *testing.T) {
|
|
// Create a server that fails the first /load but succeeds on the second /load
|
|
var callCount int
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == http.MethodPost {
|
|
callCount++
|
|
if callCount == 1 {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
if r.URL.Path == "/config/" && r.Method == http.MethodGet {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("{" + "\"apps\":{\"http\":{}}}"))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer server.Close()
|
|
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name()+"rollbackok")
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Create a host
|
|
host := models.ProxyHost{DomainNames: "rb.example.com", ForwardHost: "127.0.0.1", ForwardPort: 8080, Enabled: true}
|
|
db.Create(&host)
|
|
|
|
tmp := t.TempDir()
|
|
client := NewClient(server.URL)
|
|
manager := NewManager(client, db, tmp, "", false)
|
|
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "apply failed")
|
|
}
|
|
|
|
func TestManager_SaveSnapshot_MarshalError(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
manager := NewManager(nil, nil, tmp, "", false)
|
|
// Stub jsonMarshallFunc to return error
|
|
orig := jsonMarshalFunc
|
|
jsonMarshalFunc = func(v interface{}, prefix, indent string) ([]byte, error) {
|
|
return nil, fmt.Errorf("marshal fail")
|
|
}
|
|
defer func() { jsonMarshalFunc = orig }()
|
|
|
|
_, err := manager.saveSnapshot(&Config{})
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestManager_RotateSnapshots_DeleteError(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
// Create three files to remove one
|
|
for i := 1; i <= 3; i++ {
|
|
p := filepath.Join(tmp, fmt.Sprintf("config-%d.json", i))
|
|
os.WriteFile(p, []byte("{}"), 0644)
|
|
os.Chtimes(p, time.Now().Add(time.Duration(i)*time.Second), time.Now().Add(time.Duration(i)*time.Second))
|
|
}
|
|
|
|
manager := NewManager(nil, nil, tmp, "", false)
|
|
// Stub removeFileFunc to return error for specific path
|
|
origRemove := removeFileFunc
|
|
removeFileFunc = func(p string) error {
|
|
if filepath.Base(p) == "config-1.json" {
|
|
return fmt.Errorf("cannot delete")
|
|
}
|
|
return origRemove(p)
|
|
}
|
|
defer func() { removeFileFunc = origRemove }()
|
|
|
|
err := manager.rotateSnapshots(2)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestManager_ApplyConfig_GenerateConfigFails(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
// Setup DB - minimal
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name()+"genfail")
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Create a host so ApplyConfig tries to generate config
|
|
host := models.ProxyHost{DomainNames: "x.example.com", ForwardHost: "127.0.0.1", ForwardPort: 8080, Enabled: true}
|
|
db.Create(&host)
|
|
|
|
// stub generateConfigFunc to always return error
|
|
orig := generateConfigFunc
|
|
generateConfigFunc = func(hosts []models.ProxyHost, storageDir string, acmeEmail string, frontendDir string, sslProvider string, acmeStaging bool) (*Config, error) {
|
|
return nil, fmt.Errorf("generate fail")
|
|
}
|
|
defer func() { generateConfigFunc = orig }()
|
|
|
|
manager := NewManager(nil, db, tmp, "", false)
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "generate config")
|
|
}
|
|
|
|
func TestManager_ApplyConfig_ValidateFails(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
// Setup DB - minimal
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name()+"valfail")
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Create a host so ApplyConfig tries to generate config
|
|
host := models.ProxyHost{DomainNames: "y.example.com", ForwardHost: "127.0.0.1", ForwardPort: 8080, Enabled: true}
|
|
db.Create(&host)
|
|
|
|
// Stub validate function to return error
|
|
orig := validateConfigFunc
|
|
validateConfigFunc = func(cfg *Config) error { return fmt.Errorf("validation failed stub") }
|
|
defer func() { validateConfigFunc = orig }()
|
|
|
|
// Use a working client so generation succeeds
|
|
// Mock Caddy admin API that accepts loads
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == http.MethodPost {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
if r.URL.Path == "/config/" && r.Method == http.MethodGet {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("{" + "\"apps\":{\"http\":{}}}"))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
client := NewClient(caddyServer.URL)
|
|
manager := NewManager(client, db, tmp, "", false)
|
|
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "validation failed")
|
|
}
|
|
|
|
func TestManager_Rollback_ReadFileError(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
manager := NewManager(nil, nil, tmp, "", false)
|
|
// Create snapshot entries via write
|
|
p := filepath.Join(tmp, "config-123.json")
|
|
os.WriteFile(p, []byte(`{"apps":{"http":{}}}`), 0644)
|
|
// Stub readFileFunc to return error
|
|
origRead := readFileFunc
|
|
readFileFunc = func(p string) ([]byte, error) { return nil, fmt.Errorf("read error") }
|
|
defer func() { readFileFunc = origRead }()
|
|
|
|
err := manager.rollback(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "read snapshot")
|
|
}
|
|
|
|
func TestManager_ApplyConfig_RotateSnapshotsWarning_Stderr(t *testing.T) {
|
|
// Setup minimal DB and client that accepts load
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name()+"rotwarn")
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
host := models.ProxyHost{DomainNames: "rotwarn.example.com", ForwardHost: "127.0.0.1", ForwardPort: 8080, Enabled: true}
|
|
db.Create(&host)
|
|
|
|
// Setup Caddy server
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == http.MethodPost {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
if r.URL.Path == "/config/" && r.Method == http.MethodGet {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("{" + "\"apps\":{\"http\":{}}}"))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
// stub readDirFunc to return error to cause rotateSnapshots to fail
|
|
origReadDir := readDirFunc
|
|
readDirFunc = func(path string) ([]os.DirEntry, error) { return nil, fmt.Errorf("dir read fail") }
|
|
defer func() { readDirFunc = origReadDir }()
|
|
|
|
client := NewClient(caddyServer.URL)
|
|
manager := NewManager(client, db, t.TempDir(), "", false)
|
|
err = manager.ApplyConfig(context.Background())
|
|
// Should succeed despite rotation warning (non-fatal)
|
|
assert.NoError(t, err)
|
|
}
|