Files
pulse/internal/config/persistence_nodes_save_test.go
T
rcourtman b707512e38 Clear all errcheck and gofmt violations so make lint gates on real findings
golangci-lint run ./... failed on ~190 pre-existing errcheck violations and
5 unformatted files, burying any new regression in noise. Fix all of them:

- Test files that hand-rolled mock-mode set/restore (vmware, truenas, and
  friends) now use the canonical setMockModeForTest/testutil.SetMockMode
  helper instead of drift copies that ignored SetEnabled errors.
- internal/mock and internal/monitoring tests get package-local
  mustSetEnabled/mustSetMockEnabled/mustSetMonitorMockMode helpers that
  fail the test on toggle errors.
- pkg/auth/sqlite_manager.go, pkg/metrics/store.go, pkg/server/server.go:
  rollbacks in defers use the explicit-discard idiom, migration renames and
  rollup commits log failures, the hosted reaper goroutine logs an error
  exit, shutdown mock-disable logs failures.
- Remaining test sites check errors with t.Fatalf/t.Errorf or explicitly
  discard best-effort calls (restore-chmods, handler-closure unmarshals)
  per existing repo style.
- gofmt: internal/api/maintenance_verification.go, internal/ai/demo.go and
  three findings test files.

Only dupl findings remain (44 pre-existing production-code duplication
pairs) — those need real refactors, not mechanical fixes.

Full test suites pass for every touched package.
2026-06-09 21:42:21 +01:00

128 lines
3.8 KiB
Go

package config
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/crypto"
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
"github.com/stretchr/testify/assert"
)
func TestSaveNodesConfig_Scenarios(t *testing.T) {
tempDir := t.TempDir()
cp := NewConfigPersistence(tempDir)
nodesFile := filepath.Join(tempDir, "nodes.enc")
// 1. Mock mode enabled
t.Run("MockModeEnabled", func(t *testing.T) {
if err := mock.SetEnabled(true); err != nil {
t.Fatalf("enable mock mode: %v", err)
}
defer func() {
if err := mock.SetEnabled(false); err != nil {
t.Errorf("disable mock mode: %v", err)
}
}()
err := cp.SaveNodesConfig([]PVEInstance{{Host: "test"}}, nil, nil)
assert.NoError(t, err)
// Verify file NOT created
_, err = os.Stat(nodesFile)
assert.True(t, os.IsNotExist(err))
})
// 2. Blocked Wipe branch
t.Run("BlockedWipe", func(t *testing.T) {
// Create a non-empty config first
initialNodes := []PVEInstance{{Host: "existing"}}
validData, _ := json.Marshal(NodesConfig{PVEInstances: initialNodes})
_ = os.WriteFile(nodesFile, validData, 0600)
// Attempt to save empty config with allowEmpty=false (default for SaveNodesConfig wrapper)
err := cp.SaveNodesConfig([]PVEInstance{}, nil, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "refusing to save empty nodes config")
// Verify file still has original content
data, _ := os.ReadFile(nodesFile)
var cfg NodesConfig
_ = json.Unmarshal(data, &cfg)
assert.Equal(t, "existing", cfg.PVEInstances[0].Host)
})
// 3. Blocked Wipe with Crypto Decrypt Failure
t.Run("BlockedWipe_DecryptFailure", func(t *testing.T) {
cm, _ := crypto.NewCryptoManagerAt(tempDir)
cp.crypto = cm
// Write invalid encrypted data
_ = os.WriteFile(nodesFile, []byte("invalid-encrypted-data-too-short"), 0600)
err := cp.SaveNodesConfig([]PVEInstance{}, nil, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "existing nodes config is not decryptable")
})
// 4. Blocked Wipe with JSON Parse Failure
t.Run("BlockedWipe_ParseFailure", func(t *testing.T) {
cp.crypto = nil
_ = os.WriteFile(nodesFile, []byte("not json"), 0600)
err := cp.SaveNodesConfig([]PVEInstance{}, nil, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "existing nodes config is not parseable")
})
// 5. Success with Backups
t.Run("SuccessWithBackups", func(t *testing.T) {
_ = os.WriteFile(nodesFile, []byte("{}"), 0600) // Initial file
err := cp.SaveNodesConfig([]PVEInstance{{Host: "new"}}, nil, nil)
assert.NoError(t, err)
// Check for backup file
_, err = os.Stat(nodesFile + ".backup")
assert.NoError(t, err)
// Check for timestamped backup
matches, _ := filepath.Glob(nodesFile + ".backup-*")
assert.NotEmpty(t, matches)
})
// 6. Backup Rename Error
t.Run("BackupRenameError", func(t *testing.T) {
_ = os.WriteFile(nodesFile, []byte("{}"), 0600)
mfs := &mockFSRenameSpecific{FileSystem: defaultFileSystem{}, failPattern: ".backup"}
cp.SetFileSystem(mfs)
err := cp.SaveNodesConfig([]PVEInstance{{Host: "new"}}, nil, nil)
assert.NoError(t, err) // Should succeed despite backup error
})
// 7. Backup Write Error
t.Run("BackupWriteError", func(t *testing.T) {
_ = os.WriteFile(nodesFile, []byte("{}"), 0600)
mfs := &mockFSWriteSpecific{FileSystem: defaultFileSystem{}, failPattern: ".backup"}
cp.SetFileSystem(mfs)
err := cp.SaveNodesConfig([]PVEInstance{{Host: "new"}}, nil, nil)
assert.NoError(t, err) // Should succeed despite backup write error (logged warning)
})
// 8. Mkdir Error
t.Run("MkdirError", func(t *testing.T) {
mfs := &mockFSError{FileSystem: defaultFileSystem{}, mkdirError: os.ErrPermission}
cp.SetFileSystem(mfs)
err := cp.SaveNodesConfig([]PVEInstance{{Host: "new"}}, nil, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "permission denied")
})
}