tests
This commit is contained in:
43
internal/agents/agent_test.go
Normal file
43
internal/agents/agent_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package agents
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHistoryMessage_Fields(t *testing.T) {
|
||||
h := HistoryMessage{Role: "user", Content: "Hallo"}
|
||||
if h.Role != "user" {
|
||||
t.Errorf("Role: %q", h.Role)
|
||||
}
|
||||
if h.Content != "Hallo" {
|
||||
t.Errorf("Content: %q", h.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequest_HistoryAppend(t *testing.T) {
|
||||
req := Request{
|
||||
Action: ActionQuery,
|
||||
Args: []string{"Frage"},
|
||||
History: []HistoryMessage{
|
||||
{Role: "user", Content: "Vorherige Frage"},
|
||||
{Role: "assistant", Content: "Vorherige Antwort"},
|
||||
},
|
||||
}
|
||||
if len(req.History) != 2 {
|
||||
t.Errorf("History len: %d", len(req.History))
|
||||
}
|
||||
if req.History[0].Role != "user" {
|
||||
t.Errorf("erstes Element: %q", req.History[0].Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponse_RawAnswer(t *testing.T) {
|
||||
resp := Response{
|
||||
Text: "**Formatierte** Antwort",
|
||||
RawAnswer: "Formatierte Antwort",
|
||||
}
|
||||
if resp.RawAnswer == "" {
|
||||
t.Error("RawAnswer sollte gesetzt sein")
|
||||
}
|
||||
if resp.Text == resp.RawAnswer {
|
||||
t.Error("Text und RawAnswer sollten sich unterscheiden")
|
||||
}
|
||||
}
|
||||
236
internal/agents/task/agent_test.go
Normal file
236
internal/agents/task/agent_test.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"my-brain-importer/internal/agents"
|
||||
)
|
||||
|
||||
func newTestAgent(t *testing.T) (*Agent, func()) {
|
||||
t.Helper()
|
||||
f, err := os.CreateTemp("", "tasks_agent_*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("temp-Datei: %v", err)
|
||||
}
|
||||
name := f.Name()
|
||||
f.Close()
|
||||
os.Remove(name) // Datei entfernen → loadLocked gibt leeres Slice zurück
|
||||
a := &Agent{store: &Store{path: name}}
|
||||
return a, func() { os.Remove(name) }
|
||||
}
|
||||
|
||||
// ── parseAddArgs ─────────────────────────────────────────────────────────────
|
||||
|
||||
func TestParseAddArgs_TextOnly(t *testing.T) {
|
||||
text, prio, due := parseAddArgs([]string{"Arzttermin", "buchen"})
|
||||
if text != "Arzttermin buchen" {
|
||||
t.Errorf("text: %q", text)
|
||||
}
|
||||
if prio != "" {
|
||||
t.Errorf("prio sollte leer sein: %q", prio)
|
||||
}
|
||||
if due != nil {
|
||||
t.Error("due sollte nil sein")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAddArgs_AllFlags(t *testing.T) {
|
||||
text, prio, due := parseAddArgs([]string{"Zahnarzt", "--due", "2026-12-01", "--priority", "hoch"})
|
||||
if text != "Zahnarzt" {
|
||||
t.Errorf("text: %q", text)
|
||||
}
|
||||
if prio != "hoch" {
|
||||
t.Errorf("prio: %q", prio)
|
||||
}
|
||||
if due == nil {
|
||||
t.Fatal("due sollte gesetzt sein")
|
||||
}
|
||||
if due.Format("2006-01-02") != "2026-12-01" {
|
||||
t.Errorf("due: %v", due)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAddArgs_ShortFlags(t *testing.T) {
|
||||
text, prio, due := parseAddArgs([]string{"Meeting", "-p", "mittel", "-d", "2026-06-15"})
|
||||
if text != "Meeting" {
|
||||
t.Errorf("text: %q", text)
|
||||
}
|
||||
if prio != "mittel" {
|
||||
t.Errorf("prio: %q", prio)
|
||||
}
|
||||
if due == nil || due.Format("2006-01-02") != "2026-06-15" {
|
||||
t.Errorf("due: %v", due)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAddArgs_InvalidDate(t *testing.T) {
|
||||
_, _, due := parseAddArgs([]string{"Task", "--due", "kein-datum"})
|
||||
if due != nil {
|
||||
t.Error("ungültiges Datum sollte nil ergeben")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Agent.Handle ─────────────────────────────────────────────────────────────
|
||||
|
||||
func TestAgent_Add(t *testing.T) {
|
||||
a, cleanup := newTestAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
resp := a.Handle(agents.Request{Action: agents.ActionAdd, Args: []string{"Mein Task"}})
|
||||
if resp.Error != nil {
|
||||
t.Fatalf("Add: %v", resp.Error)
|
||||
}
|
||||
if !strings.Contains(resp.Text, "Mein Task") {
|
||||
t.Errorf("Antwort enthält keinen Task-Text: %q", resp.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Add_WithDueAndPriority(t *testing.T) {
|
||||
a, cleanup := newTestAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
resp := a.Handle(agents.Request{
|
||||
Action: agents.ActionAdd,
|
||||
Args: []string{"Frist", "--due", "2026-12-31", "--priority", "hoch"},
|
||||
})
|
||||
if resp.Error != nil {
|
||||
t.Fatalf("Add: %v", resp.Error)
|
||||
}
|
||||
if !strings.Contains(resp.Text, "Frist") {
|
||||
t.Errorf("Task-Text fehlt: %q", resp.Text)
|
||||
}
|
||||
if !strings.Contains(resp.Text, "hoch") {
|
||||
t.Errorf("Priorität fehlt: %q", resp.Text)
|
||||
}
|
||||
if !strings.Contains(resp.Text, "31.12.2026") {
|
||||
t.Errorf("Datum fehlt: %q", resp.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Add_NoArgs(t *testing.T) {
|
||||
a, cleanup := newTestAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
resp := a.Handle(agents.Request{Action: agents.ActionAdd, Args: []string{}})
|
||||
if !strings.Contains(resp.Text, "❌") {
|
||||
t.Errorf("erwartet Fehlermeldung, got: %q", resp.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_List_Empty(t *testing.T) {
|
||||
a, cleanup := newTestAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
resp := a.Handle(agents.Request{Action: agents.ActionList})
|
||||
if !strings.Contains(resp.Text, "Keine Tasks") {
|
||||
t.Errorf("erwartet 'Keine Tasks': %q", resp.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_List_ShowsDueDate(t *testing.T) {
|
||||
a, cleanup := newTestAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
due := time.Now().Add(48 * time.Hour) // übermorgen
|
||||
a.store.Add("Übermorgen", "", &due)
|
||||
|
||||
resp := a.Handle(agents.Request{Action: agents.ActionList})
|
||||
if !strings.Contains(resp.Text, "📅") {
|
||||
t.Errorf("Datum-Icon fehlt in Liste: %q", resp.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_List_ShowsOverdue(t *testing.T) {
|
||||
a, cleanup := newTestAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
past := time.Now().Add(-48 * time.Hour) // vorgestern
|
||||
a.store.Add("Überfällig", "", &past)
|
||||
|
||||
resp := a.Handle(agents.Request{Action: agents.ActionList})
|
||||
if !strings.Contains(resp.Text, "ÜBERFÄLLIG") {
|
||||
t.Errorf("ÜBERFÄLLIG fehlt: %q", resp.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Done(t *testing.T) {
|
||||
a, cleanup := newTestAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
addResp := a.Handle(agents.Request{Action: agents.ActionAdd, Args: []string{"Erledigen"}})
|
||||
// Short-ID aus Antwort extrahieren
|
||||
tasks, _ := a.store.Load()
|
||||
if len(tasks) == 0 {
|
||||
t.Fatal("kein Task angelegt")
|
||||
}
|
||||
id := tasks[0].ID
|
||||
shortID := id[len(id)-6:]
|
||||
|
||||
_ = addResp
|
||||
resp := a.Handle(agents.Request{Action: agents.ActionDone, Args: []string{shortID}})
|
||||
if resp.Error != nil {
|
||||
t.Fatalf("Done: %v", resp.Error)
|
||||
}
|
||||
if !strings.Contains(resp.Text, "✅") {
|
||||
t.Errorf("erwartet ✅: %q", resp.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_Delete(t *testing.T) {
|
||||
a, cleanup := newTestAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
a.Handle(agents.Request{Action: agents.ActionAdd, Args: []string{"Löschen"}})
|
||||
tasks, _ := a.store.Load()
|
||||
id := tasks[0].ID
|
||||
shortID := id[len(id)-6:]
|
||||
|
||||
resp := a.Handle(agents.Request{Action: agents.ActionDelete, Args: []string{shortID}})
|
||||
if resp.Error != nil {
|
||||
t.Fatalf("Delete: %v", resp.Error)
|
||||
}
|
||||
if !strings.Contains(resp.Text, "🗑️") {
|
||||
t.Errorf("erwartet 🗑️: %q", resp.Text)
|
||||
}
|
||||
|
||||
tasks, _ = a.store.Load()
|
||||
if len(tasks) != 0 {
|
||||
t.Errorf("Task sollte gelöscht sein, noch %d vorhanden", len(tasks))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgent_UnknownAction(t *testing.T) {
|
||||
a, cleanup := newTestAgent(t)
|
||||
defer cleanup()
|
||||
|
||||
resp := a.Handle(agents.Request{Action: "unknown"})
|
||||
if !strings.Contains(resp.Text, "❌") {
|
||||
t.Errorf("erwartet Fehlermeldung: %q", resp.Text)
|
||||
}
|
||||
}
|
||||
|
||||
// ── resolveID ────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestResolveID_FullMatch(t *testing.T) {
|
||||
tasks := []Task{{ID: "123456789"}}
|
||||
if got := resolveID(tasks, "123456789"); got != "123456789" {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveID_ShortMatch(t *testing.T) {
|
||||
tasks := []Task{{ID: "123456789"}}
|
||||
if got := resolveID(tasks, "456789"); got != "123456789" {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveID_NotFound(t *testing.T) {
|
||||
tasks := []Task{{ID: "123456789"}}
|
||||
if got := resolveID(tasks, "000000"); got != "" {
|
||||
t.Errorf("erwartet leer, got %q", got)
|
||||
}
|
||||
}
|
||||
169
internal/agents/task/store_test.go
Normal file
169
internal/agents/task/store_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newTestStore(t *testing.T) (*Store, func()) {
|
||||
t.Helper()
|
||||
f, err := os.CreateTemp("", "tasks_test_*.json")
|
||||
if err != nil {
|
||||
t.Fatalf("temp-Datei erstellen: %v", err)
|
||||
}
|
||||
name := f.Name()
|
||||
f.Close()
|
||||
os.Remove(name) // Datei entfernen → loadLocked gibt leeres Slice zurück
|
||||
s := &Store{path: name}
|
||||
return s, func() { os.Remove(name) }
|
||||
}
|
||||
|
||||
func TestStore_AddAndLoad(t *testing.T) {
|
||||
s, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
task, err := s.Add("Test Task", "hoch", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
if task.Text != "Test Task" {
|
||||
t.Errorf("Text: got %q, want %q", task.Text, "Test Task")
|
||||
}
|
||||
if task.Priority != "hoch" {
|
||||
t.Errorf("Priority: got %q, want %q", task.Priority, "hoch")
|
||||
}
|
||||
if task.Done {
|
||||
t.Error("neuer Task sollte nicht Done sein")
|
||||
}
|
||||
|
||||
tasks, err := s.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
if len(tasks) != 1 {
|
||||
t.Fatalf("len: got %d, want 1", len(tasks))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_AddWithDueDate(t *testing.T) {
|
||||
s, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
due := time.Date(2026, 12, 31, 0, 0, 0, 0, time.UTC)
|
||||
task, err := s.Add("Frist-Task", "mittel", &due)
|
||||
if err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
if task.DueDate == nil {
|
||||
t.Fatal("DueDate sollte gesetzt sein")
|
||||
}
|
||||
if !task.DueDate.Equal(due) {
|
||||
t.Errorf("DueDate: got %v, want %v", task.DueDate, due)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_MarkDone(t *testing.T) {
|
||||
s, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
task, _ := s.Add("Erledigbarer Task", "", nil)
|
||||
if err := s.MarkDone(task.ID); err != nil {
|
||||
t.Fatalf("MarkDone: %v", err)
|
||||
}
|
||||
|
||||
tasks, _ := s.Load()
|
||||
if !tasks[0].Done {
|
||||
t.Error("Task sollte Done=true sein")
|
||||
}
|
||||
if tasks[0].DoneAt == nil {
|
||||
t.Error("DoneAt sollte gesetzt sein")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_MarkDone_NotFound(t *testing.T) {
|
||||
s, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
err := s.MarkDone("nichtexistent")
|
||||
if err == nil {
|
||||
t.Error("erwartet Fehler für unbekannte ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_Delete(t *testing.T) {
|
||||
s, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
t1, _ := s.Add("Task 1", "", nil)
|
||||
s.Add("Task 2", "", nil)
|
||||
|
||||
if err := s.Delete(t1.ID); err != nil {
|
||||
t.Fatalf("Delete: %v", err)
|
||||
}
|
||||
|
||||
tasks, _ := s.Load()
|
||||
if len(tasks) != 1 {
|
||||
t.Fatalf("nach Delete: got %d, want 1", len(tasks))
|
||||
}
|
||||
if tasks[0].Text != "Task 2" {
|
||||
t.Errorf("falscher Task verblieben: %q", tasks[0].Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_Delete_NotFound(t *testing.T) {
|
||||
s, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
err := s.Delete("nichtexistent")
|
||||
if err == nil {
|
||||
t.Error("erwartet Fehler für unbekannte ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_OpenTasks(t *testing.T) {
|
||||
s, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
t1, _ := s.Add("Offen", "", nil)
|
||||
s.Add("Auch offen", "", nil)
|
||||
s.MarkDone(t1.ID)
|
||||
|
||||
open, err := s.OpenTasks()
|
||||
if err != nil {
|
||||
t.Fatalf("OpenTasks: %v", err)
|
||||
}
|
||||
if len(open) != 1 {
|
||||
t.Fatalf("OpenTasks: got %d, want 1", len(open))
|
||||
}
|
||||
if open[0].Text != "Auch offen" {
|
||||
t.Errorf("falscher offener Task: %q", open[0].Text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_EmptyFile(t *testing.T) {
|
||||
s, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
tasks, err := s.Load()
|
||||
if err != nil {
|
||||
t.Fatalf("Load auf leerer Datei: %v", err)
|
||||
}
|
||||
if len(tasks) != 0 {
|
||||
t.Errorf("erwartet leer, got %d", len(tasks))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_Idempotent_MultipleAdds(t *testing.T) {
|
||||
s, cleanup := newTestStore(t)
|
||||
defer cleanup()
|
||||
|
||||
s.Add("A", "", nil)
|
||||
s.Add("B", "niedrig", nil)
|
||||
s.Add("C", "hoch", nil)
|
||||
|
||||
tasks, _ := s.Load()
|
||||
if len(tasks) != 3 {
|
||||
t.Fatalf("erwartet 3 Tasks, got %d", len(tasks))
|
||||
}
|
||||
}
|
||||
72
internal/agents/tool/email/summary_test.go
Normal file
72
internal/agents/tool/email/summary_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var testMessages = []Message{
|
||||
{Subject: "Rechnung März", From: "buchhaltung@firma.de", Date: "2026-03-01 09:00"},
|
||||
{Subject: "Meeting Einladung", From: "chef@firma.de", Date: "2026-03-02 10:30"},
|
||||
{Subject: "Newsletter", From: "news@shop.de", Date: "2026-03-03 08:00"},
|
||||
}
|
||||
|
||||
func TestFormatEmailList_ContainsFields(t *testing.T) {
|
||||
result := formatEmailList(testMessages)
|
||||
|
||||
checks := []string{"Rechnung März", "buchhaltung@firma.de", "Meeting Einladung", "Newsletter"}
|
||||
for _, s := range checks {
|
||||
if !strings.Contains(result, s) {
|
||||
t.Errorf("formatEmailList: fehlt %q in:\n%s", s, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatEmailList_NumberedLines(t *testing.T) {
|
||||
result := formatEmailList(testMessages)
|
||||
if !strings.Contains(result, "[1]") {
|
||||
t.Error("fehlt [1] in Ausgabe")
|
||||
}
|
||||
if !strings.Contains(result, "[3]") {
|
||||
t.Error("fehlt [3] in Ausgabe")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatEmailList_Empty(t *testing.T) {
|
||||
result := formatEmailList([]Message{})
|
||||
if result != "" {
|
||||
t.Errorf("leere Liste sollte leeren String ergeben, got: %q", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallbackList_ContainsWarning(t *testing.T) {
|
||||
result := fallbackList(testMessages)
|
||||
if !strings.Contains(result, "LLM nicht verfügbar") {
|
||||
t.Errorf("Fallback-Hinweis fehlt: %q", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallbackList_ContainsAllMessages(t *testing.T) {
|
||||
result := fallbackList(testMessages)
|
||||
for _, m := range testMessages {
|
||||
if !strings.Contains(result, m.Subject) {
|
||||
t.Errorf("Betreff fehlt: %q", m.Subject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMessages_Empty(t *testing.T) {
|
||||
result := parseMessages(nil)
|
||||
if len(result) != 0 {
|
||||
t.Errorf("erwartet leer, got %d", len(result))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessage_DateFormat(t *testing.T) {
|
||||
// Datum muss im Format "2006-01-02 15:04" formatiert werden
|
||||
m := testMessages[0]
|
||||
if _, err := time.Parse("2006-01-02 15:04", m.Date); err != nil {
|
||||
t.Errorf("Datumsformat ungültig: %v", err)
|
||||
}
|
||||
}
|
||||
120
internal/diag/diag.go
Normal file
120
internal/diag/diag.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// diag – Start-Diagnose: prüft Erreichbarkeit aller externen Dienste
|
||||
package diag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"my-brain-importer/internal/config"
|
||||
)
|
||||
|
||||
// Result ist das Ergebnis einer einzelnen Prüfung.
|
||||
type Result struct {
|
||||
Name string
|
||||
OK bool
|
||||
Message string
|
||||
}
|
||||
|
||||
// RunAll führt alle Verbindungs-Checks durch und gibt eine Zusammenfassung zurück.
|
||||
// warnings = Dienste die konfiguriert aber nicht erreichbar sind.
|
||||
func RunAll() (results []Result, allOK bool) {
|
||||
allOK = true
|
||||
cfg := config.Cfg
|
||||
|
||||
check := func(name string, ok bool, msg string) {
|
||||
results = append(results, Result{Name: name, OK: ok, Message: msg})
|
||||
if !ok {
|
||||
allOK = false
|
||||
}
|
||||
}
|
||||
|
||||
// Qdrant
|
||||
qdrantAddr := fmt.Sprintf("%s:%s", cfg.Qdrant.Host, cfg.Qdrant.Port)
|
||||
ok, msg := tcpCheck(qdrantAddr)
|
||||
check("Qdrant ("+qdrantAddr+")", ok, msg)
|
||||
|
||||
// LocalAI Chat
|
||||
if cfg.Chat.URL != "" {
|
||||
ok, msg = httpCheck(cfg.Chat.URL)
|
||||
check("LocalAI Chat ("+cfg.Chat.URL+")", ok, msg)
|
||||
}
|
||||
|
||||
// LocalAI Embedding (nur prüfen wenn andere URL als Chat)
|
||||
if cfg.Embedding.URL != "" && cfg.Embedding.URL != cfg.Chat.URL {
|
||||
ok, msg = httpCheck(cfg.Embedding.URL)
|
||||
check("LocalAI Embedding ("+cfg.Embedding.URL+")", ok, msg)
|
||||
}
|
||||
|
||||
// IMAP
|
||||
if cfg.Email.Host != "" {
|
||||
imapAddr := fmt.Sprintf("%s:%d", cfg.Email.Host, cfg.Email.Port)
|
||||
ok, msg = tcpCheck(imapAddr)
|
||||
check("IMAP ("+imapAddr+")", ok, msg)
|
||||
}
|
||||
|
||||
return results, allOK
|
||||
}
|
||||
|
||||
// Log gibt die Ergebnisse über slog aus.
|
||||
func Log(results []Result) {
|
||||
for _, r := range results {
|
||||
if r.OK {
|
||||
slog.Info("Dienst-Check", "dienst", r.Name, "status", "OK", "info", r.Message)
|
||||
} else {
|
||||
slog.Warn("Dienst-Check", "dienst", r.Name, "status", "FEHLER", "info", r.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format gibt eine menschenlesbare Zusammenfassung zurück (für Discord/stdout).
|
||||
func Format(results []Result, allOK bool) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("🔍 **Start-Diagnose:**\n")
|
||||
for _, r := range results {
|
||||
icon := "✅"
|
||||
if !r.OK {
|
||||
icon = "❌"
|
||||
}
|
||||
fmt.Fprintf(&sb, "%s %s – %s\n", icon, r.Name, r.Message)
|
||||
}
|
||||
if allOK {
|
||||
sb.WriteString("\n✅ Alle Dienste erreichbar.")
|
||||
} else {
|
||||
sb.WriteString("\n⚠️ Einige Dienste sind nicht erreichbar — Bot läuft, aber Funktionen könnten fehlen.")
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func tcpCheck(addr string) (bool, string) {
|
||||
conn, err := net.DialTimeout("tcp", addr, 3*time.Second)
|
||||
if err != nil {
|
||||
return false, "nicht erreichbar: " + err.Error()
|
||||
}
|
||||
conn.Close()
|
||||
return true, "TCP OK"
|
||||
}
|
||||
|
||||
func httpCheck(baseURL string) (bool, string) {
|
||||
// Normalisiere URL: entferne trailing /v1 etc., hänge /v1/models an
|
||||
url := strings.TrimRight(baseURL, "/")
|
||||
if !strings.HasSuffix(url, "/models") {
|
||||
// Gehe zur Basis-URL zurück und frage /v1/models
|
||||
if idx := strings.LastIndex(url, "/v1"); idx >= 0 {
|
||||
url = url[:idx]
|
||||
}
|
||||
url += "/v1/models"
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 3 * time.Second}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false, "nicht erreichbar: " + err.Error()
|
||||
}
|
||||
resp.Body.Close()
|
||||
return resp.StatusCode == http.StatusOK,
|
||||
fmt.Sprintf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
Reference in New Issue
Block a user