1package modes
2
3import (
4 "errors"
5 "strings"
6 "testing"
7
8 "github.com/patriceckhart/zot/packages/agent/extensions"
9 "github.com/patriceckhart/zot/packages/core"
10 "github.com/patriceckhart/zot/packages/tui"
11)
12
13func TestFormatReloadStatusIncludesErrorDetails(t *testing.T) {
14 msg, failed := formatReloadStatus(extensions.ReloadStats{
15 Stopped: 2,
16 Loaded: 1,
17 Ready: 1,
18 Errors: []error{
19 errors.New("broken manifest"),
20 errors.New("spawn worker: executable not found"),
21 },
22 })
23 if !failed {
24 t.Fatal("reload with errors was not marked failed")
25 }
26 for _, want := range []string{
27 "2 stopped, 1 loaded (1 ready)",
28 "2 error(s)",
29 "broken manifest",
30 "spawn worker: executable not found",
31 } {
32 if !strings.Contains(msg, want) {
33 t.Fatalf("reload status missing %q: %s", want, msg)
34 }
35 }
36}
37
38func TestRenderReloadErrorsPreservesMultilineAlignment(t *testing.T) {
39 lines := renderReloadErrors(tui.Theme{Error: 1}, []string{"failed to start\nextension directory: /tmp/ext\nstderr log: /tmp/ext.log"}, 80)
40 if len(lines) != 3 {
41 t.Fatalf("rendered %d lines, want 3: %q", len(lines), lines)
42 }
43 if !strings.Contains(lines[0], "✖ failed to start") {
44 t.Fatalf("first line = %q, want error marker", lines[0])
45 }
46 for _, line := range lines[1:] {
47 if !strings.Contains(line, " ") {
48 t.Fatalf("continuation line = %q, want indentation", line)
49 }
50 }
51}
52
53func TestFormatReloadStatusSuccess(t *testing.T) {
54 msg, failed := formatReloadStatus(extensions.ReloadStats{Stopped: 2, Loaded: 3, Ready: 3})
55 if failed {
56 t.Fatal("successful reload was marked failed")
57 }
58 if want := "reloaded: 2 stopped, 3 loaded (3 ready)"; msg != want {
59 t.Fatalf("reload status = %q, want %q", msg, want)
60 }
61}
62
63func TestSetReloadStatusUsesErrorChannelForFailures(t *testing.T) {
64 i := &Interactive{}
65 i.setReloadStatus("broken extension", true)
66 if i.statusErr != "broken extension" || i.statusOK != "" {
67 t.Fatalf("failed reload status used wrong channel: err=%q ok=%q", i.statusErr, i.statusOK)
68 }
69 if len(i.reloadErrors) != 1 || i.reloadErrors[0] != "broken extension" {
70 t.Fatalf("reload errors = %q, want persistent failure", i.reloadErrors)
71 }
72 i.setReloadStatus("reload complete", false)
73 if i.statusOK != "reload complete" || i.statusErr != "" {
74 t.Fatalf("successful reload status used wrong channel: err=%q ok=%q", i.statusErr, i.statusOK)
75 }
76 if len(i.reloadErrors) != 0 {
77 t.Fatalf("reload errors = %q after successful reload, want none", i.reloadErrors)
78 }
79}
80
81func TestStartupExtensionFailureAppearsInChatWithoutEnteringContext(t *testing.T) {
82 agent := core.NewAgent(nil, "", "", nil)
83 i := NewInteractive(InteractiveConfig{
84 Agent: agent,
85 Theme: tui.Theme{Error: 1},
86 StartupExtensionErrors: []string{"startup failed: stderr log: /tmp/startup.log"},
87 })
88
89 chat := strings.Join(i.buildChatLocked(30), "\n")
90 if !strings.Contains(chat, "startup failed") || !strings.Contains(chat, "/tmp/startup.log") {
91 t.Fatalf("chat does not contain startup extension failure: %q", chat)
92 }
93 if got := len(agent.Messages()); got != 0 {
94 t.Fatalf("agent context contains %d startup error message(s), want none", got)
95 }
96}
97
98func TestReloadFailureRemainsInChatWithoutEnteringContext(t *testing.T) {
99 agent := core.NewAgent(nil, "", "", nil)
100 i := NewInteractive(InteractiveConfig{
101 Agent: agent,
102 Theme: tui.Theme{Error: 1},
103 })
104 seq := i.setReloadStatus("broken extension: stderr log: /tmp/ext.log", true)
105 if !i.clearReloadStatus(seq, i.statusErr, true) {
106 t.Fatal("temporary reload status was not cleared")
107 }
108
109 chat := strings.Join(i.buildChatLocked(30), "\n")
110 if !strings.Contains(chat, "broken extension") || !strings.Contains(chat, "/tmp/ext.log") {
111 t.Fatalf("chat does not contain persistent reload failure: %q", chat)
112 }
113 if got := len(agent.Messages()); got != 0 {
114 t.Fatalf("agent context contains %d reload error message(s), want none", got)
115 }
116}
117
118func TestClearReloadStatusDoesNotClearNewerStatus(t *testing.T) {
119 i := &Interactive{statusErr: "newer error", statusOK: "newer success"}
120 if i.clearReloadStatus(0, "old error", true) {
121 t.Fatal("stale error dismissal reported a clear")
122 }
123 if i.clearReloadStatus(0, "old success", false) {
124 t.Fatal("stale success dismissal reported a clear")
125 }
126 if i.statusErr != "newer error" || i.statusOK != "newer success" {
127 t.Fatalf("stale dismissal changed status: err=%q ok=%q", i.statusErr, i.statusOK)
128 }
129
130 if !i.clearReloadStatus(0, "newer error", true) || i.statusErr != "" {
131 t.Fatalf("matching error status was not cleared: %q", i.statusErr)
132 }
133 if !i.clearReloadStatus(0, "newer success", false) || i.statusOK != "" {
134 t.Fatalf("matching success status was not cleared: %q", i.statusOK)
135 }
136
137 first := i.setReloadStatus("same result", false)
138 second := i.setReloadStatus("same result", false)
139 if first == second {
140 t.Fatal("reload status generation did not advance")
141 }
142 if i.clearReloadStatus(first, "same result", false) {
143 t.Fatal("older identical reload status cleared the newer result")
144 }
145}