Mining/pkg/mining/repository_test.go
Claude 1f9624279a
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
ax(batch): rename abbreviated variables to predictable names
resp→response, db→database pattern across mining, logging packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 04:47:58 +01:00

402 lines
10 KiB
Go

package mining
import (
"errors"
"os"
"path/filepath"
"testing"
)
type testData struct {
Name string `json:"name"`
Value int `json:"value"`
}
func TestFileRepository_Load_Good(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_Good(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_Good(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_Good(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_Good(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_Bad(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_Bad(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_Good(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_Bad(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_Ugly(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)
}
}
// repo := NewFileRepository[[]item](path, WithDefaults(func() []item { return []item{} }))
// repo.Save(items); repo.Update(func(data *[]item) error { *data = append(*data, item{...}); return nil })
func TestFileRepository_SliceData_Good(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))
}
}