Files
nadir-agent/internal/modules/services/services_handler_test.go
T
urania 2bf11dda91
build-and-release / release (push) Failing after 17m7s
feat: first release
2026-06-22 16:51:18 +02:00

155 lines
5.5 KiB
Go

package services
import (
"encoding/json"
"net/http"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"nadir/internal/oscmd"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humago"
"github.com/danielgtaylor/huma/v2/humatest"
)
func TestServicesHandlers(t *testing.T) {
mux := http.NewServeMux()
api := humatest.Wrap(t, humago.New(mux, huma.DefaultConfig("Test", "1.0.0")))
// Set up allowlisted log files for testing the file source
logFiles := map[string][]string{
"nginx.service": {filepath.Join(t.TempDir(), "nginx-error.log")},
}
// Create the dummy log file
errLogPath := logFiles["nginx.service"][0]
if err := os.WriteFile(errLogPath, []byte("file log line 1\nfile log line 2\n"), 0644); err != nil {
t.Fatal(err)
}
m := New(logFiles)
m.Register(api)
// 1. Test GET /api/services (list services)
oscmd.SetMock("systemctl", func(args []string) oscmd.MockCommand {
if reflect.DeepEqual(args, []string{"list-units", "--type=service", "--all", "-o", "json", "--no-pager"}) {
units := []ServiceUnit{
{Unit: "sshd.service", Load: "loaded", Active: "active", Sub: "running", Description: "OpenSSH"},
}
data, _ := json.Marshal(units)
return oscmd.MockCommand{Stdout: string(data) + "\n", ExitCode: 0}
}
if reflect.DeepEqual(args, []string{"show", "-p", "Id", "-p", "Description", "-p", "LoadState", "-p", "ActiveState", "-p", "SubState", "-p", "UnitFileState", "--", "sshd.service"}) {
showOut := "Id=sshd.service\nDescription=OpenSSH\nLoadState=loaded\nActiveState=active\nSubState=running\nUnitFileState=enabled\n"
return oscmd.MockCommand{Stdout: showOut, ExitCode: 0}
}
if reflect.DeepEqual(args, []string{"start", "--", "sshd.service"}) {
return oscmd.MockCommand{ExitCode: 0}
}
return oscmd.MockCommand{ExitCode: 1}
})
defer oscmd.ClearMocks()
resp := api.Get("/api/services")
if resp.Code != http.StatusOK {
t.Errorf("list services: got %d, want %d", resp.Code, http.StatusOK)
}
var listRes ListServicesOutput
if err := json.Unmarshal(resp.Body.Bytes(), &listRes.Body); err != nil {
t.Fatal(err)
}
if len(listRes.Body.Services) != 1 || listRes.Body.Services[0].Unit != "sshd.service" {
t.Errorf("list services output: %+v", listRes.Body)
}
// 2. Test GET /api/services/{unit} (get service status)
resp = api.Get("/api/services/sshd.service")
if resp.Code != http.StatusOK {
t.Errorf("get service status: got %d, want %d", resp.Code, http.StatusOK)
}
var getRes GetServiceOutput
if err := json.Unmarshal(resp.Body.Bytes(), &getRes.Body); err != nil {
t.Fatal(err)
}
if getRes.Body.Unit != "sshd.service" || getRes.Body.ActiveState != "active" {
t.Errorf("get service output: %+v", getRes.Body)
}
// 3. Test POST /api/services/{unit}/start
resp = api.Post("/api/services/sshd.service/start", struct{}{})
if resp.Code != http.StatusOK {
t.Errorf("start service: got %d, want %d", resp.Code, http.StatusOK)
}
// 4. Test GET /api/services/{unit}/logs (journal source)
oscmd.SetMock("journalctl", func(args []string) oscmd.MockCommand {
if strings.Contains(strings.Join(args, " "), "-f") {
// Streaming mock
lines := []string{
`{"MESSAGE":"streaming line 1","PRIORITY":"6","__REALTIME_TIMESTAMP":"1718873704000000"}`,
}
return oscmd.MockCommand{Lines: lines, DelayMs: 1, ExitCode: 0}
}
// Regular snapshot mock
lines := []string{
`{"MESSAGE":"journal line 1","PRIORITY":"6","__REALTIME_TIMESTAMP":"1718873704000000"}`,
}
return oscmd.MockCommand{Stdout: strings.Join(lines, "\n") + "\n", ExitCode: 0}
})
resp = api.Get("/api/services/sshd.service/logs")
if resp.Code != http.StatusOK {
t.Errorf("get journal logs: got %d, want %d", resp.Code, http.StatusOK)
}
var logsRes LogsOutput
if err := json.Unmarshal(resp.Body.Bytes(), &logsRes.Body); err != nil {
t.Fatal(err)
}
if len(logsRes.Body.Entries) != 1 || logsRes.Body.Entries[0].Message != "journal line 1" {
t.Errorf("journal logs output: %+v", logsRes.Body)
}
// 5. Test GET /api/services/{unit}/logs (file source)
oscmd.SetMock("tail", func(args []string) oscmd.MockCommand {
if strings.Contains(strings.Join(args, " "), "-F") {
// Streaming mock
return oscmd.MockCommand{Lines: []string{"stream file line 1"}, DelayMs: 1, ExitCode: 0}
}
return oscmd.MockCommand{Stdout: "file log line 1\nfile log line 2\n", ExitCode: 0}
})
resp = api.Get("/api/services/nginx.service/logs?source=file&path=" + errLogPath)
if resp.Code != http.StatusOK {
t.Errorf("get file logs: got %d, want %d", resp.Code, http.StatusOK)
}
if err := json.Unmarshal(resp.Body.Bytes(), &logsRes.Body); err != nil {
t.Fatal(err)
}
if len(logsRes.Body.Entries) != 2 || logsRes.Body.Entries[0].Message != "file log line 1" {
t.Errorf("file logs output: %+v", logsRes.Body)
}
// 6. Test GET /api/services/{unit}/logs/stream (journal stream)
resp = api.Get("/api/services/sshd.service/logs/stream")
if resp.Code != http.StatusOK {
t.Errorf("stream journal logs: got %d, want %d", resp.Code, http.StatusOK)
}
bodyStr := resp.Body.String()
if !strings.Contains(bodyStr, "streaming line 1") {
t.Errorf("stream journal logs missing message, got: %q", bodyStr)
}
// 7. Test GET /api/services/{unit}/logs/stream (file stream)
resp = api.Get("/api/services/nginx.service/logs/stream?source=file&path=" + errLogPath)
if resp.Code != http.StatusOK {
t.Errorf("stream file logs: got %d, want %d", resp.Code, http.StatusOK)
}
bodyStr = resp.Body.String()
if !strings.Contains(bodyStr, "stream file line 1") {
t.Errorf("stream file logs missing message, got: %q", bodyStr)
}
}