package di import ( "context" "testing" "time" "go.uber.org/fx" ) func TestNewContainer(t *testing.T) { t.Parallel() container := NewContainer() if container == nil { t.Fatal("NewContainer returned nil") } if container.app == nil { t.Fatal("Container app is nil") } } func TestNewContainer_WithOptions(t *testing.T) { t.Parallel() var called bool opt := fx.Invoke(func() { called = true }) container := NewContainer(opt) if container == nil { t.Fatal("NewContainer returned nil") } // Start the container to trigger the invoke ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Start in a goroutine since Start blocks go func() { _ = container.Start(ctx) }() // Give it a moment to start time.Sleep(100 * time.Millisecond) // Stop the container if err := container.Stop(ctx); err != nil { t.Errorf("Stop failed: %v", err) } if !called { t.Error("Custom option was not invoked") } } func TestContainer_Stop(t *testing.T) { t.Parallel() container := NewContainer() if container == nil { t.Fatal("NewContainer returned nil") } ctx := context.Background() // Start the container first if err := container.app.Start(ctx); err != nil { t.Fatalf("Failed to start container: %v", err) } // Stop should work without error stopCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := container.Stop(stopCtx); err != nil { t.Errorf("Stop failed: %v", err) } } func TestContainer_Stop_WithoutStart(t *testing.T) { t.Parallel() container := NewContainer() if container == nil { t.Fatal("NewContainer returned nil") } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Stop should work even if container wasn't started // (FX handles this gracefully) err := container.Stop(ctx) // It's okay if it errors or not, as long as it doesn't panic _ = err } func TestContainer_getShutdownTimeout(t *testing.T) { t.Parallel() container := NewContainer() if container == nil { t.Fatal("NewContainer returned nil") } timeout := container.getShutdownTimeout() expected := 30 * time.Second if timeout != expected { t.Errorf("getShutdownTimeout() = %v, want %v", timeout, expected) } } func TestContainer_Start_WithSignal(t *testing.T) { t.Parallel() container := NewContainer() if container == nil { t.Fatal("NewContainer returned nil") } ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Start in a goroutine startErr := make(chan error, 1) go func() { startErr <- container.Start(ctx) }() // Wait a bit for startup time.Sleep(100 * time.Millisecond) // Note: Start() waits for OS signals (SIGINT, SIGTERM). // To test this properly, we'd need to send a signal, but that requires // process control which is complex in tests. // This test verifies that Start() can be called and the container is functional. // The actual signal handling is tested in integration tests or manually. // Instead, verify that the container started successfully by checking // that app.Start() completed (no immediate error) // Then stop the container gracefully stopCtx, stopCancel := context.WithTimeout(context.Background(), 2*time.Second) defer stopCancel() // Stop should work even if Start is waiting for signals // (In a real scenario, a signal would trigger shutdown) if err := container.Stop(stopCtx); err != nil { t.Logf("Stop returned error (may be expected if Start hasn't fully initialized): %v", err) } // Cancel context to help cleanup cancel() // Give a moment for cleanup, but don't wait for Start to return // since it's blocked on signal channel time.Sleep(100 * time.Millisecond) } func TestContainer_CoreModule(t *testing.T) { t.Parallel() // Test that CoreModule provides config and logger container := NewContainer( fx.Invoke(func( // These would be provided by CoreModule // We're just checking that the container can be created ) { }), ) if container == nil { t.Fatal("NewContainer returned nil") } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Start should work if err := container.app.Start(ctx); err != nil { // It's okay if it fails due to missing config files in test environment // We're just checking that the container structure is correct t.Logf("Start failed (expected in test env): %v", err) } // Stop should always work if err := container.Stop(ctx); err != nil { t.Errorf("Stop failed: %v", err) } }