From d98aead35dcd64b7f252a68822321f68513bc7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Sch=C3=A4fer?= Date: Wed, 10 Dec 2025 16:30:11 +0100 Subject: [PATCH] maintenance: manager refactoring, testing --- cmd/run/run.go | 9 +- internal/manager/manager.go | 11 ++- internal/manager/manager_test.go | 150 +++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 internal/manager/manager_test.go diff --git a/cmd/run/run.go b/cmd/run/run.go index 7bcd64c..1f123ab 100644 --- a/cmd/run/run.go +++ b/cmd/run/run.go @@ -5,6 +5,10 @@ Licensed under the MIT License, see LICENSE file in the project root for details package run import ( + "context" + "os/signal" + "syscall" + "github.com/spf13/cobra" "github.com/tschaefer/finch/internal/manager" ) @@ -36,5 +40,8 @@ func runCmd(cmd *cobra.Command, args []string) { manager, err := manager.New(config) cobra.CheckErr(err) - manager.Run(listen) + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + manager.Run(ctx, listen) } diff --git a/internal/manager/manager.go b/internal/manager/manager.go index e300240..6b01725 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -5,11 +5,10 @@ Licensed under the MIT License, see LICENSE file in the project root for details package manager import ( + "context" "log/slog" "net" "os" - "os/signal" - "syscall" "github.com/tschaefer/finch/api" "github.com/tschaefer/finch/internal/config" @@ -65,11 +64,11 @@ func New(cfgFile string) (*Manager, error) { }, nil } -func (m *Manager) Run(listenAddr string) { +func (m *Manager) Run(ctx context.Context, listenAddr string) { slog.Debug("Running Manager", "listenAddr", listenAddr) - stop := make(chan os.Signal, 1) - signal.Notify(stop, os.Interrupt, syscall.SIGTERM) + ctx, cancel := context.WithCancel(ctx) + defer cancel() slog.Info("Starting Finch management server", "release", version.Release(), "commit", version.Commit()) slog.Info("Listening on " + listenAddr) @@ -80,7 +79,7 @@ func (m *Manager) Run(listenAddr string) { os.Exit(1) } - <-stop + <-ctx.Done() slog.Info("Shutting down server...") grpcServer.GracefulStop() diff --git a/internal/manager/manager_test.go b/internal/manager/manager_test.go new file mode 100644 index 0000000..bf2807c --- /dev/null +++ b/internal/manager/manager_test.go @@ -0,0 +1,150 @@ +package manager + +import ( + "context" + "encoding/json" + "net" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/tschaefer/finch/internal/config" +) + +func Test_NewReturnsManager(t *testing.T) { + data := config.Data{ + CreatedAt: "2025-01-01T00:00:00Z", + Database: "sqlite://:memory:", + Hostname: "finch.example.com", + Id: "8d134b24c2541730", + Secret: "C7LVMO6YY0ZfZvlEayQJR0zOE7JF8g+nrYgrcvetIbU=", + Version: "1.4.0", + Credentials: config.Credentials{ + Username: "admin", + Password: "gnKuT>m8T@3hX", + }, + } + json, err := json.MarshalIndent(data, "", " ") + if err != nil { + t.Fatal(err) + } + + tmpDir := t.TempDir() + cfgFile := tmpDir + "/finch.json" + err = os.WriteFile(cfgFile, json, 0644) + if err != nil { + t.Fatal(err) + } + + m, err := New(cfgFile) + assert.NoError(t, err) + assert.NotNil(t, m) +} + +func Test_NewReturnsError_MissingConfigFile(t *testing.T) { + _, err := New("nonexistent_file.json") + assert.Error(t, err) +} + +func Test_NewReturnsError_InvalidConfigFile(t *testing.T) { + tmpDir := t.TempDir() + cfgFile := tmpDir + "/finch.json" + err := os.WriteFile(cfgFile, []byte("invalid json"), 0644) + if err != nil { + t.Fatal(err) + } + + m, err := New(cfgFile) + assert.Nil(t, m) + assert.Error(t, err) +} + +func Test_NewReturnsError_InvalidDatabaseURL(t *testing.T) { + data := config.Data{ + CreatedAt: "2025-01-01T00:00:00Z", + Database: "invalid_db_url", + Hostname: "finch.example.com", + Id: "8d134b24c2541730", + Secret: "C7LVMO6YY0ZfZvlEayQJR0zOE7JF8g+nrYgrcvetIbU=", + Version: "1.4.0", + Credentials: config.Credentials{ + Username: "admin", + Password: "gnKuT>m8T@3hX", + }, + } + json, err := json.MarshalIndent(data, "", " ") + if err != nil { + t.Fatal(err) + } + + tmpDir := t.TempDir() + cfgFile := tmpDir + "/finch.json" + err = os.WriteFile(cfgFile, json, 0644) + if err != nil { + t.Fatal(err) + } + + m, err := New(cfgFile) + assert.Nil(t, m) + assert.Error(t, err) +} + +func Test_RunSucceeds(t *testing.T) { + data := config.Data{ + CreatedAt: "2025-01-01T00:00:00Z", + Database: "sqlite://:memory:", + Hostname: "finch.example.com", + Id: "8d134b24c2541730", + Secret: "C7LVMO6YY0ZfZvlEayQJR0zOE7JF8g+nrYgrcvetIbU=", + Version: "1.4.0", + Credentials: config.Credentials{ + Username: "admin", + Password: "gnKuT>m8T@3hX", + }, + } + json, err := json.MarshalIndent(data, "", " ") + if err != nil { + t.Fatal(err) + } + + tmpDir := t.TempDir() + cfgFile := tmpDir + "/finch.json" + err = os.WriteFile(cfgFile, json, 0644) + if err != nil { + t.Fatal(err) + } + + m, err := New(cfgFile) + assert.NoError(t, err) + assert.NotNil(t, m) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var port string + for _, port = range []string{"11111", "22222", "33333", "44444", "55555", "66666"} { + conn, _ := net.Dial("tcp", net.JoinHostPort("127.0.0.1", port)) + if conn == nil { + break + } + _ = conn.Close() + } + + go m.Run(ctx, net.JoinHostPort("127.0.0.1", port)) + + var conn net.Conn + for range 50 { + conn, err = net.Dial("tcp", net.JoinHostPort("127.0.0.1", port)) + if conn != nil { + break + } + time.Sleep(100 * time.Millisecond) + } + assert.NoError(t, err) + assert.NotNil(t, conn) + _ = conn.Close() + + cancel() + time.Sleep(100 * time.Millisecond) +}