Mining/pkg/mining/repository_test.go
snider 185bfd13dd test: Add error path unit tests for context cancellation and cleanup
- Add context cancellation tests for database InsertHashratePoint
- Add context timeout tests for database operations
- Add NopStore context handling tests
- Add container shutdown timeout and double-shutdown tests
- Add repository concurrent update, corrupt file, and permission tests
- Verify all error paths handle edge cases gracefully

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 13:04:20 +00:00

401 lines
9.7 KiB
Go

package mining
import (
"errors"
"os"
"path/filepath"
"testing"
)
type testData struct {
Name string `json:"name"`
Value int `json:"value"`
}
func TestFileRepository_Load(t *testing.T) {
t.Run("NonExistentFile", func(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "nonexistent.json")
repo := NewFileRepository[testData](path)
data, err := repo.Load()
if err != nil {
t.Fatalf("Load should not error for non-existent file: %v", err)
}
if data.Name != "" || data.Value != 0 {
t.Error("Expected zero value for non-existent file")
}
})
t.Run("NonExistentFileWithDefaults", func(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "nonexistent.json")
repo := NewFileRepository[testData](path, WithDefaults(func() testData {
return testData{Name: "default", Value: 42}
}))
data, err := repo.Load()
if err != nil {
t.Fatalf("Load should not error: %v", err)
}
if data.Name != "default" || data.Value != 42 {
t.Errorf("Expected default values, got %+v", data)
}
})
t.Run("ExistingFile", func(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "test.json")
// Write test data
if err := os.WriteFile(path, []byte(`{"name":"test","value":123}`), 0600); err != nil {
t.Fatalf("Failed to write test file: %v", err)
}
repo := NewFileRepository[testData](path)
data, err := repo.Load()
if err != nil {
t.Fatalf("Load failed: %v", err)
}
if data.Name != "test" || data.Value != 123 {
t.Errorf("Unexpected data: %+v", data)
}
})
t.Run("InvalidJSON", func(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "invalid.json")
if err := os.WriteFile(path, []byte(`{invalid json}`), 0600); err != nil {
t.Fatalf("Failed to write test file: %v", err)
}
repo := NewFileRepository[testData](path)
_, err := repo.Load()
if err == nil {
t.Error("Expected error for invalid JSON")
}
})
}
func TestFileRepository_Save(t *testing.T) {
t.Run("NewFile", func(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "subdir", "new.json")
repo := NewFileRepository[testData](path)
data := testData{Name: "saved", Value: 456}
if err := repo.Save(data); err != nil {
t.Fatalf("Save failed: %v", err)
}
// Verify file was created
if !repo.Exists() {
t.Error("File should exist after save")
}
// Verify content
loaded, err := repo.Load()
if err != nil {
t.Fatalf("Load after save failed: %v", err)
}
if loaded.Name != "saved" || loaded.Value != 456 {
t.Errorf("Unexpected loaded data: %+v", loaded)
}
})
t.Run("OverwriteExisting", func(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "existing.json")
repo := NewFileRepository[testData](path)
// Save initial data
if err := repo.Save(testData{Name: "first", Value: 1}); err != nil {
t.Fatalf("First save failed: %v", err)
}
// Overwrite
if err := repo.Save(testData{Name: "second", Value: 2}); err != nil {
t.Fatalf("Second save failed: %v", err)
}
// Verify overwrite
loaded, err := repo.Load()
if err != nil {
t.Fatalf("Load failed: %v", err)
}
if loaded.Name != "second" || loaded.Value != 2 {
t.Errorf("Expected overwritten data, got: %+v", loaded)
}
})
}
func TestFileRepository_Update(t *testing.T) {
t.Run("UpdateExisting", func(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "update.json")
repo := NewFileRepository[testData](path)
// Save initial data
if err := repo.Save(testData{Name: "initial", Value: 10}); err != nil {
t.Fatalf("Initial save failed: %v", err)
}
// Update
err := repo.Update(func(data *testData) error {
data.Value += 5
return nil
})
if err != nil {
t.Fatalf("Update failed: %v", err)
}
// Verify update
loaded, err := repo.Load()
if err != nil {
t.Fatalf("Load failed: %v", err)
}
if loaded.Value != 15 {
t.Errorf("Expected value 15, got %d", loaded.Value)
}
})
t.Run("UpdateNonExistentWithDefaults", func(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "new.json")
repo := NewFileRepository[testData](path, WithDefaults(func() testData {
return testData{Name: "default", Value: 100}
}))
err := repo.Update(func(data *testData) error {
data.Value *= 2
return nil
})
if err != nil {
t.Fatalf("Update failed: %v", err)
}
// Verify update started from defaults
loaded, err := repo.Load()
if err != nil {
t.Fatalf("Load failed: %v", err)
}
if loaded.Value != 200 {
t.Errorf("Expected value 200, got %d", loaded.Value)
}
})
t.Run("UpdateWithError", func(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "error.json")
repo := NewFileRepository[testData](path)
if err := repo.Save(testData{Name: "test", Value: 1}); err != nil {
t.Fatalf("Initial save failed: %v", err)
}
// Update that returns error
testErr := errors.New("update error")
err := repo.Update(func(data *testData) error {
data.Value = 999 // This change should not be saved
return testErr
})
if err != testErr {
t.Errorf("Expected test error, got: %v", err)
}
// Verify original data unchanged
loaded, err := repo.Load()
if err != nil {
t.Fatalf("Load failed: %v", err)
}
if loaded.Value != 1 {
t.Errorf("Expected value 1 (unchanged), got %d", loaded.Value)
}
})
}
func TestFileRepository_Delete(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "delete.json")
repo := NewFileRepository[testData](path)
// Save data
if err := repo.Save(testData{Name: "temp", Value: 1}); err != nil {
t.Fatalf("Save failed: %v", err)
}
if !repo.Exists() {
t.Error("File should exist after save")
}
// Delete
if err := repo.Delete(); err != nil {
t.Fatalf("Delete failed: %v", err)
}
if repo.Exists() {
t.Error("File should not exist after delete")
}
// Delete non-existent should not error
if err := repo.Delete(); err != nil {
t.Errorf("Delete non-existent should not error: %v", err)
}
}
func TestFileRepository_Path(t *testing.T) {
path := "/some/path/config.json"
repo := NewFileRepository[testData](path)
if repo.Path() != path {
t.Errorf("Expected path %s, got %s", path, repo.Path())
}
}
func TestFileRepository_UpdateWithLoadError(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "corrupt.json")
repo := NewFileRepository[testData](path)
// Write invalid JSON
if err := os.WriteFile(path, []byte(`{invalid}`), 0600); err != nil {
t.Fatalf("Failed to write corrupt file: %v", err)
}
// Update should fail to load the corrupt file
err := repo.Update(func(data *testData) error {
data.Value = 999
return nil
})
if err == nil {
t.Error("Expected error for corrupt file during Update")
}
}
func TestFileRepository_SaveToReadOnlyDirectory(t *testing.T) {
if os.Getuid() == 0 {
t.Skip("Test skipped when running as root")
}
tmpDir := t.TempDir()
readOnlyDir := filepath.Join(tmpDir, "readonly")
if err := os.Mkdir(readOnlyDir, 0555); err != nil {
t.Fatalf("Failed to create readonly dir: %v", err)
}
defer os.Chmod(readOnlyDir, 0755) // Restore permissions for cleanup
path := filepath.Join(readOnlyDir, "test.json")
repo := NewFileRepository[testData](path)
// Save should fail due to permission denied
err := repo.Save(testData{Name: "test", Value: 1})
if err == nil {
t.Error("Expected error when saving to read-only directory")
}
}
func TestFileRepository_DeleteNonExistent(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "nonexistent.json")
repo := NewFileRepository[testData](path)
// Delete on non-existent file should not error
if err := repo.Delete(); err != nil {
t.Errorf("Delete on non-existent file should not error: %v", err)
}
}
func TestFileRepository_ExistsOnInvalidPath(t *testing.T) {
// Use a path that definitely doesn't exist
repo := NewFileRepository[testData]("/nonexistent/path/to/file.json")
if repo.Exists() {
t.Error("Exists should return false for invalid path")
}
}
func TestFileRepository_ConcurrentUpdates(t *testing.T) {
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "concurrent.json")
repo := NewFileRepository[testData](path, WithDefaults(func() testData {
return testData{Name: "initial", Value: 0}
}))
// Run multiple concurrent updates
const numUpdates = 10
done := make(chan bool)
for i := 0; i < numUpdates; i++ {
go func() {
err := repo.Update(func(data *testData) error {
data.Value++
return nil
})
if err != nil {
t.Logf("Concurrent update error: %v", err)
}
done <- true
}()
}
// Wait for all updates
for i := 0; i < numUpdates; i++ {
<-done
}
// Verify final value equals number of updates
data, err := repo.Load()
if err != nil {
t.Fatalf("Load failed: %v", err)
}
if data.Value != numUpdates {
t.Errorf("Expected value %d after concurrent updates, got %d", numUpdates, data.Value)
}
}
// Test with slice data
func TestFileRepository_SliceData(t *testing.T) {
type item struct {
ID string `json:"id"`
Name string `json:"name"`
}
tmpDir := t.TempDir()
path := filepath.Join(tmpDir, "items.json")
repo := NewFileRepository[[]item](path, WithDefaults(func() []item {
return []item{}
}))
// Save slice
items := []item{
{ID: "1", Name: "First"},
{ID: "2", Name: "Second"},
}
if err := repo.Save(items); err != nil {
t.Fatalf("Save failed: %v", err)
}
// Load and verify
loaded, err := repo.Load()
if err != nil {
t.Fatalf("Load failed: %v", err)
}
if len(loaded) != 2 {
t.Errorf("Expected 2 items, got %d", len(loaded))
}
// Update slice
err = repo.Update(func(data *[]item) error {
*data = append(*data, item{ID: "3", Name: "Third"})
return nil
})
if err != nil {
t.Fatalf("Update failed: %v", err)
}
loaded, _ = repo.Load()
if len(loaded) != 3 {
t.Errorf("Expected 3 items after update, got %d", len(loaded))
}
}