summaryrefslogtreecommitdiff
path: root/internal/hexaicli/run_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/hexaicli/run_test.go')
-rw-r--r--internal/hexaicli/run_test.go196
1 files changed, 112 insertions, 84 deletions
diff --git a/internal/hexaicli/run_test.go b/internal/hexaicli/run_test.go
index 0d77e19..77daa8b 100644
--- a/internal/hexaicli/run_test.go
+++ b/internal/hexaicli/run_test.go
@@ -1,122 +1,150 @@
package hexaicli
import (
- "bytes"
- "context"
- "io"
- "path/filepath"
- "strings"
- "testing"
+ "bytes"
+ "context"
+ "io"
+ "path/filepath"
+ "strings"
+ "testing"
- "codeberg.org/snonux/hexai/internal/appconfig"
- "codeberg.org/snonux/hexai/internal/llm"
+ "codeberg.org/snonux/hexai/internal/appconfig"
+ "codeberg.org/snonux/hexai/internal/llm"
)
func TestReadInput_Combinations(t *testing.T) {
- // stdin + arg
- restore, f := setStdin(t, "from-stdin")
- defer restore()
- s, err := readInput(f, []string{"from-arg"})
- if err != nil || !strings.HasPrefix(s, "from-arg:\n\nfrom-stdin") { t.Fatalf("stdin+arg failed: %q %v", s, err) }
- // stdin only
- restore2, f2 := setStdin(t, "from-stdin")
- defer restore2()
- s, err = readInput(f2, nil)
- if err != nil || s != "from-stdin" { t.Fatalf("stdin only failed: %q %v", s, err) }
- // arg only
- s, err = readInput(strings.NewReader(""), []string{"arg1","arg2"})
- if err != nil || s != "arg1 arg2" { t.Fatalf("arg only failed: %q %v", s, err) }
- // no input
- restore3, f3 := setStdin(t, "")
- defer restore3()
- _, err = readInput(f3, nil)
- if err == nil { t.Fatalf("expected error for no input") }
+ // stdin + arg
+ restore, f := setStdin(t, "from-stdin")
+ defer restore()
+ s, err := readInput(f, []string{"from-arg"})
+ if err != nil || !strings.HasPrefix(s, "from-arg:\n\nfrom-stdin") {
+ t.Fatalf("stdin+arg failed: %q %v", s, err)
+ }
+ // stdin only
+ restore2, f2 := setStdin(t, "from-stdin")
+ defer restore2()
+ s, err = readInput(f2, nil)
+ if err != nil || s != "from-stdin" {
+ t.Fatalf("stdin only failed: %q %v", s, err)
+ }
+ // arg only
+ s, err = readInput(strings.NewReader(""), []string{"arg1", "arg2"})
+ if err != nil || s != "arg1 arg2" {
+ t.Fatalf("arg only failed: %q %v", s, err)
+ }
+ // no input
+ restore3, f3 := setStdin(t, "")
+ defer restore3()
+ _, err = readInput(f3, nil)
+ if err == nil {
+ t.Fatalf("expected error for no input")
+ }
}
func TestBuildMessages_Explain(t *testing.T) {
- msgs := buildMessages("please explain this")
- if len(msgs) != 2 || msgs[0].Role != "system" || !strings.Contains(strings.ToLower(msgs[0].Content), "explanation") {
- t.Fatalf("unexpected system prompt: %#v", msgs)
- }
+ msgs := buildMessages("please explain this")
+ if len(msgs) != 2 || msgs[0].Role != "system" || !strings.Contains(strings.ToLower(msgs[0].Content), "explanation") {
+ t.Fatalf("unexpected system prompt: %#v", msgs)
+ }
}
func TestBuildMessages_Default(t *testing.T) {
- msgs := buildMessages("just do it")
- if len(msgs) != 2 || msgs[0].Role != "system" || strings.Contains(msgs[0].Content, "requested an explanation") {
- t.Fatalf("unexpected system prompt: %#v", msgs)
- }
+ msgs := buildMessages("just do it")
+ if len(msgs) != 2 || msgs[0].Role != "system" || strings.Contains(msgs[0].Content, "requested an explanation") {
+ t.Fatalf("unexpected system prompt: %#v", msgs)
+ }
}
func TestRunChat_StreamAndNonStream(t *testing.T) {
- // stream path
- fc := &fakeStreamer{fakeClient: fakeClient{name: "p", model: "m"}, chunks: []string{"H","i","!"}}
- var out, errb bytes.Buffer
- if err := runChat(context.Background(), fc, buildMessages("hello"), "hello", &out, &errb); err != nil { t.Fatalf("stream: %v", err) }
- if out.String() != "Hi!" || !strings.Contains(errb.String(), "provider=p model=m") { t.Fatalf("bad output or summary: %q %q", out.String(), errb.String()) }
- // non-stream path
- fc2 := &fakeClient{name: "p2", model: "m2", resp: "Yo"}
- out.Reset(); errb.Reset()
- if err := runChat(context.Background(), fc2, buildMessages("hello"), "hello", &out, &errb); err != nil { t.Fatalf("non-stream: %v", err) }
- if out.String() != "Yo" || !strings.Contains(errb.String(), "provider=p2 model=m2") { t.Fatalf("bad output or summary (non-stream)") }
+ // stream path
+ fc := &fakeStreamer{fakeClient: fakeClient{name: "p", model: "m"}, chunks: []string{"H", "i", "!"}}
+ var out, errb bytes.Buffer
+ if err := runChat(context.Background(), fc, buildMessages("hello"), "hello", &out, &errb); err != nil {
+ t.Fatalf("stream: %v", err)
+ }
+ if out.String() != "Hi!" || !strings.Contains(errb.String(), "provider=p model=m") {
+ t.Fatalf("bad output or summary: %q %q", out.String(), errb.String())
+ }
+ // non-stream path
+ fc2 := &fakeClient{name: "p2", model: "m2", resp: "Yo"}
+ out.Reset()
+ errb.Reset()
+ if err := runChat(context.Background(), fc2, buildMessages("hello"), "hello", &out, &errb); err != nil {
+ t.Fatalf("non-stream: %v", err)
+ }
+ if out.String() != "Yo" || !strings.Contains(errb.String(), "provider=p2 model=m2") {
+ t.Fatalf("bad output or summary (non-stream)")
+ }
}
type clientErr struct{ name, model string }
-func (c clientErr) Chat(context.Context, []llm.Message, ...llm.RequestOption) (string, error) { return "", io.EOF }
-func (c clientErr) Name() string { return c.name }
+
+func (c clientErr) Chat(context.Context, []llm.Message, ...llm.RequestOption) (string, error) {
+ return "", io.EOF
+}
+func (c clientErr) Name() string { return c.name }
func (c clientErr) DefaultModel() string { return c.model }
func TestRunChat_ErrorPaths(t *testing.T) {
- ctx := context.Background()
- out, errb := &bytes.Buffer{}, &bytes.Buffer{}
- if err := runChat(ctx, clientErr{"p","m"}, buildMessages("hi"), "hi", out, errb); err == nil {
- t.Fatalf("expected error from Chat")
- }
+ ctx := context.Background()
+ out, errb := &bytes.Buffer{}, &bytes.Buffer{}
+ if err := runChat(ctx, clientErr{"p", "m"}, buildMessages("hi"), "hi", out, errb); err == nil {
+ t.Fatalf("expected error from Chat")
+ }
}
func TestRunWithClient_ErrorPrint(t *testing.T) {
- var out, errb bytes.Buffer
- err := RunWithClient(context.Background(), []string{"hi"}, strings.NewReader(""), &out, &errb, clientErr{"p","m"})
- if err == nil { t.Fatalf("expected error") }
- if !strings.Contains(errb.String(), "hexai: error:") {
- t.Fatalf("expected error line, got %q", errb.String())
- }
+ var out, errb bytes.Buffer
+ err := RunWithClient(context.Background(), []string{"hi"}, strings.NewReader(""), &out, &errb, clientErr{"p", "m"})
+ if err == nil {
+ t.Fatalf("expected error")
+ }
+ if !strings.Contains(errb.String(), "hexai: error:") {
+ t.Fatalf("expected error line, got %q", errb.String())
+ }
}
func TestRun_OpenAI_NoKey_ShowsError(t *testing.T) {
- dir := testingTempDir(t)
- // write config with provider=openai
- writeJSON(t, filepath.Join(dir, "hexai", "config.json"), map[string]any{"provider":"openai", "openai_model":"gpt-x"})
- t.Setenv("XDG_CONFIG_HOME", dir)
- // Ensure no OpenAI API key is present in environment
- t.Setenv("HEXAI_OPENAI_API_KEY", "")
- t.Setenv("OPENAI_API_KEY", "")
- var out, errb bytes.Buffer
- // Run expects parsed flags; here args irrelevant
- err := Run(context.Background(), []string{"hello"}, strings.NewReader(""), &out, &errb)
- if err == nil { t.Fatalf("expected error due to missing API key") }
- // Accept either explicit "LLM disabled" or a generic provider error emitted by Run.
- if !(strings.Contains(errb.String(), "LLM disabled") || strings.Contains(errb.String(), "openai error") || strings.Contains(errb.String(), "hexai: error:")) {
- t.Fatalf("expected disabled-or-error message, got %q", errb.String())
- }
+ dir := testingTempDir(t)
+ // write config with provider=openai
+ writeJSON(t, filepath.Join(dir, "hexai", "config.json"), map[string]any{"provider": "openai", "openai_model": "gpt-x"})
+ t.Setenv("XDG_CONFIG_HOME", dir)
+ // Ensure no OpenAI API key is present in environment
+ t.Setenv("HEXAI_OPENAI_API_KEY", "")
+ t.Setenv("OPENAI_API_KEY", "")
+ var out, errb bytes.Buffer
+ // Run expects parsed flags; here args irrelevant
+ err := Run(context.Background(), []string{"hello"}, strings.NewReader(""), &out, &errb)
+ if err == nil {
+ t.Fatalf("expected error due to missing API key")
+ }
+ // Accept either explicit "LLM disabled" or a generic provider error emitted by Run.
+ if !(strings.Contains(errb.String(), "LLM disabled") || strings.Contains(errb.String(), "openai error") || strings.Contains(errb.String(), "hexai: error:")) {
+ t.Fatalf("expected disabled-or-error message, got %q", errb.String())
+ }
}
func TestPrintProviderInfo(t *testing.T) {
- var b bytes.Buffer
- printProviderInfo(&b, &fakeClient{name:"x", model:"y"})
- if !strings.Contains(b.String(), "provider=x model=y") { t.Fatalf("missing provider line: %q", b.String()) }
+ var b bytes.Buffer
+ printProviderInfo(&b, &fakeClient{name: "x", model: "y"})
+ if !strings.Contains(b.String(), "provider=x model=y") {
+ t.Fatalf("missing provider line: %q", b.String())
+ }
}
func TestNewClientFromConfig_Ollama(t *testing.T) {
- cfg := appconfig.App{ Provider: "ollama", OllamaBaseURL: "http://x", OllamaModel: "m" }
- c, err := newClientFromConfig(cfg)
- if err != nil || c == nil { t.Fatalf("expected client: %v %v", c, err) }
+ cfg := appconfig.App{Provider: "ollama", OllamaBaseURL: "http://x", OllamaModel: "m"}
+ c, err := newClientFromConfig(cfg)
+ if err != nil || c == nil {
+ t.Fatalf("expected client: %v %v", c, err)
+ }
}
func TestNewClientFromConfig_OpenAI_MissingKey(t *testing.T) {
- cfg := appconfig.App{ Provider: "openai", OpenAIBaseURL: "https://api", OpenAIModel: "gpt" }
- t.Setenv("HEXAI_OPENAI_API_KEY", "")
- t.Setenv("OPENAI_API_KEY", "")
- if _, err := newClientFromConfig(cfg); err == nil {
- t.Fatalf("expected error for missing openai key")
- }
+ cfg := appconfig.App{Provider: "openai", OpenAIBaseURL: "https://api", OpenAIModel: "gpt"}
+ t.Setenv("HEXAI_OPENAI_API_KEY", "")
+ t.Setenv("OPENAI_API_KEY", "")
+ if _, err := newClientFromConfig(cfg); err == nil {
+ t.Fatalf("expected error for missing openai key")
+ }
}