- 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>
316 lines
7.7 KiB
Go
316 lines
7.7 KiB
Go
package mining
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Snider/Mining/pkg/database"
|
|
)
|
|
|
|
func setupContainerTestEnv(t *testing.T) func() {
|
|
tmpDir := t.TempDir()
|
|
os.Setenv("XDG_CONFIG_HOME", filepath.Join(tmpDir, "config"))
|
|
os.Setenv("XDG_DATA_HOME", filepath.Join(tmpDir, "data"))
|
|
return func() {
|
|
os.Unsetenv("XDG_CONFIG_HOME")
|
|
os.Unsetenv("XDG_DATA_HOME")
|
|
}
|
|
}
|
|
|
|
func TestNewContainer(t *testing.T) {
|
|
config := DefaultContainerConfig()
|
|
container := NewContainer(config)
|
|
|
|
if container == nil {
|
|
t.Fatal("NewContainer returned nil")
|
|
}
|
|
|
|
if container.IsInitialized() {
|
|
t.Error("Container should not be initialized before Initialize() is called")
|
|
}
|
|
}
|
|
|
|
func TestDefaultContainerConfig(t *testing.T) {
|
|
config := DefaultContainerConfig()
|
|
|
|
if !config.Database.Enabled {
|
|
t.Error("Database should be enabled by default")
|
|
}
|
|
|
|
if config.Database.RetentionDays != 30 {
|
|
t.Errorf("Expected 30 retention days, got %d", config.Database.RetentionDays)
|
|
}
|
|
|
|
if config.ListenAddr != ":9090" {
|
|
t.Errorf("Expected :9090, got %s", config.ListenAddr)
|
|
}
|
|
|
|
if config.SimulationMode {
|
|
t.Error("SimulationMode should be false by default")
|
|
}
|
|
}
|
|
|
|
func TestContainer_Initialize(t *testing.T) {
|
|
cleanup := setupContainerTestEnv(t)
|
|
defer cleanup()
|
|
|
|
config := DefaultContainerConfig()
|
|
config.Database.Enabled = true
|
|
config.Database.Path = filepath.Join(t.TempDir(), "test.db")
|
|
config.SimulationMode = true // Use simulation mode for faster tests
|
|
|
|
container := NewContainer(config)
|
|
ctx := context.Background()
|
|
|
|
if err := container.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
if !container.IsInitialized() {
|
|
t.Error("Container should be initialized after Initialize()")
|
|
}
|
|
|
|
// Verify services are available
|
|
if container.Manager() == nil {
|
|
t.Error("Manager should not be nil after initialization")
|
|
}
|
|
|
|
if container.ProfileManager() == nil {
|
|
t.Error("ProfileManager should not be nil after initialization")
|
|
}
|
|
|
|
if container.EventHub() == nil {
|
|
t.Error("EventHub should not be nil after initialization")
|
|
}
|
|
|
|
if container.HashrateStore() == nil {
|
|
t.Error("HashrateStore should not be nil after initialization")
|
|
}
|
|
|
|
// Cleanup
|
|
if err := container.Shutdown(ctx); err != nil {
|
|
t.Errorf("Shutdown failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestContainer_InitializeTwice(t *testing.T) {
|
|
cleanup := setupContainerTestEnv(t)
|
|
defer cleanup()
|
|
|
|
config := DefaultContainerConfig()
|
|
config.Database.Enabled = false
|
|
config.SimulationMode = true
|
|
|
|
container := NewContainer(config)
|
|
ctx := context.Background()
|
|
|
|
if err := container.Initialize(ctx); err != nil {
|
|
t.Fatalf("First Initialize failed: %v", err)
|
|
}
|
|
|
|
// Second initialization should fail
|
|
if err := container.Initialize(ctx); err == nil {
|
|
t.Error("Second Initialize should fail")
|
|
}
|
|
|
|
container.Shutdown(ctx)
|
|
}
|
|
|
|
func TestContainer_DatabaseDisabled(t *testing.T) {
|
|
cleanup := setupContainerTestEnv(t)
|
|
defer cleanup()
|
|
|
|
config := DefaultContainerConfig()
|
|
config.Database.Enabled = false
|
|
config.SimulationMode = true
|
|
|
|
container := NewContainer(config)
|
|
ctx := context.Background()
|
|
|
|
if err := container.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
// Should use NopStore when database is disabled
|
|
store := container.HashrateStore()
|
|
if store == nil {
|
|
t.Fatal("HashrateStore should not be nil")
|
|
}
|
|
|
|
// NopStore should accept inserts without error
|
|
point := database.HashratePoint{
|
|
Timestamp: time.Now(),
|
|
Hashrate: 1000,
|
|
}
|
|
if err := store.InsertHashratePoint(nil, "test", "xmrig", point, database.ResolutionHigh); err != nil {
|
|
t.Errorf("NopStore insert should not fail: %v", err)
|
|
}
|
|
|
|
container.Shutdown(ctx)
|
|
}
|
|
|
|
func TestContainer_SetHashrateStore(t *testing.T) {
|
|
cleanup := setupContainerTestEnv(t)
|
|
defer cleanup()
|
|
|
|
config := DefaultContainerConfig()
|
|
config.Database.Enabled = false
|
|
config.SimulationMode = true
|
|
|
|
container := NewContainer(config)
|
|
ctx := context.Background()
|
|
|
|
if err := container.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
// Inject custom store
|
|
customStore := database.NopStore()
|
|
container.SetHashrateStore(customStore)
|
|
|
|
if container.HashrateStore() != customStore {
|
|
t.Error("SetHashrateStore should update the store")
|
|
}
|
|
|
|
container.Shutdown(ctx)
|
|
}
|
|
|
|
func TestContainer_StartWithoutInitialize(t *testing.T) {
|
|
config := DefaultContainerConfig()
|
|
container := NewContainer(config)
|
|
ctx := context.Background()
|
|
|
|
if err := container.Start(ctx); err == nil {
|
|
t.Error("Start should fail if Initialize was not called")
|
|
}
|
|
}
|
|
|
|
func TestContainer_ShutdownWithoutInitialize(t *testing.T) {
|
|
config := DefaultContainerConfig()
|
|
container := NewContainer(config)
|
|
ctx := context.Background()
|
|
|
|
// Shutdown on uninitialized container should not error
|
|
if err := container.Shutdown(ctx); err != nil {
|
|
t.Errorf("Shutdown on uninitialized container should not error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestContainer_ShutdownChannel(t *testing.T) {
|
|
cleanup := setupContainerTestEnv(t)
|
|
defer cleanup()
|
|
|
|
config := DefaultContainerConfig()
|
|
config.Database.Enabled = false
|
|
config.SimulationMode = true
|
|
|
|
container := NewContainer(config)
|
|
ctx := context.Background()
|
|
|
|
if err := container.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
shutdownCh := container.ShutdownCh()
|
|
|
|
// Channel should be open before shutdown
|
|
select {
|
|
case <-shutdownCh:
|
|
t.Error("ShutdownCh should not be closed before Shutdown()")
|
|
default:
|
|
// Expected
|
|
}
|
|
|
|
if err := container.Shutdown(ctx); err != nil {
|
|
t.Errorf("Shutdown failed: %v", err)
|
|
}
|
|
|
|
// Channel should be closed after shutdown
|
|
select {
|
|
case <-shutdownCh:
|
|
// Expected
|
|
case <-time.After(time.Second):
|
|
t.Error("ShutdownCh should be closed after Shutdown()")
|
|
}
|
|
}
|
|
|
|
func TestContainer_InitializeWithCancelledContext(t *testing.T) {
|
|
cleanup := setupContainerTestEnv(t)
|
|
defer cleanup()
|
|
|
|
config := DefaultContainerConfig()
|
|
config.Database.Enabled = false
|
|
config.SimulationMode = true
|
|
|
|
container := NewContainer(config)
|
|
|
|
// Use a pre-cancelled context
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
|
|
// Initialize should still succeed (context is checked at operation start)
|
|
// But operations that check context should respect cancellation
|
|
if err := container.Initialize(ctx); err != nil {
|
|
// This is acceptable - initialization may fail with cancelled context
|
|
t.Logf("Initialize with cancelled context: %v (acceptable)", err)
|
|
}
|
|
|
|
// Cleanup if initialized
|
|
if container.IsInitialized() {
|
|
container.Shutdown(context.Background())
|
|
}
|
|
}
|
|
|
|
func TestContainer_ShutdownWithTimeout(t *testing.T) {
|
|
cleanup := setupContainerTestEnv(t)
|
|
defer cleanup()
|
|
|
|
config := DefaultContainerConfig()
|
|
config.Database.Enabled = false
|
|
config.SimulationMode = true
|
|
|
|
container := NewContainer(config)
|
|
ctx := context.Background()
|
|
|
|
if err := container.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
// Use a context with very short timeout
|
|
timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
|
defer cancel()
|
|
|
|
// Shutdown should still complete (cleanup is fast without real miners)
|
|
if err := container.Shutdown(timeoutCtx); err != nil {
|
|
t.Logf("Shutdown with timeout: %v (may be acceptable)", err)
|
|
}
|
|
}
|
|
|
|
func TestContainer_DoubleShutdown(t *testing.T) {
|
|
cleanup := setupContainerTestEnv(t)
|
|
defer cleanup()
|
|
|
|
config := DefaultContainerConfig()
|
|
config.Database.Enabled = false
|
|
config.SimulationMode = true
|
|
|
|
container := NewContainer(config)
|
|
ctx := context.Background()
|
|
|
|
if err := container.Initialize(ctx); err != nil {
|
|
t.Fatalf("Initialize failed: %v", err)
|
|
}
|
|
|
|
// First shutdown
|
|
if err := container.Shutdown(ctx); err != nil {
|
|
t.Errorf("First shutdown failed: %v", err)
|
|
}
|
|
|
|
// Second shutdown should not panic or error
|
|
if err := container.Shutdown(ctx); err != nil {
|
|
t.Logf("Second shutdown returned: %v (expected no-op)", err)
|
|
}
|
|
}
|