Adds 'hyperguild brain pass-rate <skill> [--window 7d] [--json]' calling the new /pass-rate endpoint. Human output: tdd: 47 / 50 = 94% (window: 7d) or 'no data (window: 7d)' when pass_rate is null. PassRateResult mirrors the response envelope; PassRate is *float64 so null is preserved across decode.
221 lines
7.2 KiB
Go
221 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func brainQueryServer(t *testing.T, body string) *httptest.Server {
|
|
t.Helper()
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(body))
|
|
}))
|
|
}
|
|
|
|
func TestRunBrainQuery_Human(t *testing.T) {
|
|
srv := brainQueryServer(t, `{"results":[{"path":"knowledge/a.md","title":"A","excerpt":"...","score":9},{"path":"knowledge/b.md","title":"B","excerpt":"...","score":3}]}`)
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"query", "topic"}, strings.NewReader(""), &out, &errBuf)
|
|
require.NoError(t, err)
|
|
got := out.String()
|
|
assert.Contains(t, got, "knowledge/a.md")
|
|
assert.Contains(t, got, "score=9")
|
|
assert.Contains(t, got, "knowledge/b.md")
|
|
}
|
|
|
|
func TestRunBrainQuery_JSON(t *testing.T) {
|
|
srv := brainQueryServer(t, `{"results":[{"path":"x.md","title":"X","excerpt":"e","score":5}]}`)
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"query", "--json", "topic"}, strings.NewReader(""), &out, &errBuf)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, out.String(), `"path": "x.md"`)
|
|
assert.Contains(t, out.String(), `"score": 5`)
|
|
}
|
|
|
|
func TestRunBrainQuery_Limit(t *testing.T) {
|
|
gotLimit := -1
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, _ := io.ReadAll(r.Body)
|
|
var p struct {
|
|
Query string `json:"query"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
_ = json.Unmarshal(body, &p)
|
|
gotLimit = p.Limit
|
|
_, _ = w.Write([]byte(`{"results":[]}`))
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"query", "--limit", "12", "topic"}, strings.NewReader(""), &out, &errBuf)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 12, gotLimit)
|
|
}
|
|
|
|
func TestRunBrainQuery_MissingTopic(t *testing.T) {
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"query"}, strings.NewReader(""), &out, &errBuf)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestRunBrain_NoSubsubcommand(t *testing.T) {
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{}, strings.NewReader(""), &out, &errBuf)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "subcommand required")
|
|
}
|
|
|
|
func TestRunBrain_UnknownSubsubcommand(t *testing.T) {
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"bogus"}, strings.NewReader(""), &out, &errBuf)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestRunBrainWrite_Success(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, http.MethodPost, r.Method)
|
|
assert.Equal(t, "/write", r.URL.Path)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"path":"knowledge/test-slug.md"}`))
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(
|
|
context.Background(),
|
|
[]string{"write", "knowledge", "test-slug"},
|
|
strings.NewReader("# Test\n\nSome body content.\n"),
|
|
&out, &errBuf,
|
|
)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, out.String(), "knowledge/test-slug.md")
|
|
}
|
|
|
|
func TestRunBrainWrite_MissingArgs(t *testing.T) {
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"write", "knowledge"}, strings.NewReader("x"), &out, &errBuf)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "type and slug required")
|
|
}
|
|
|
|
func TestRunBrainWrite_BackendError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_, _ = w.Write([]byte("invalid slug"))
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(
|
|
context.Background(),
|
|
[]string{"write", "knowledge", "bad slug"},
|
|
strings.NewReader("body"),
|
|
&out, &errBuf,
|
|
)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "400")
|
|
}
|
|
|
|
func TestRunBrainWrite_EmptyStdin(t *testing.T) {
|
|
gotLen := -1
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, _ := io.ReadAll(r.Body)
|
|
var p struct {
|
|
Content string `json:"content"`
|
|
}
|
|
_ = json.Unmarshal(body, &p)
|
|
gotLen = len(p.Content)
|
|
_, _ = w.Write([]byte(`{"path":"x.md"}`))
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"write", "knowledge", "empty"}, strings.NewReader(""), &out, &errBuf)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, gotLen, "empty stdin should produce empty content payload")
|
|
}
|
|
|
|
func TestRunBrainPassRate_Human(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"skill":"tdd","window":"7d","pass":47,"fail":3,"skip":0,"total":50,"pass_rate":0.94}`))
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"pass-rate", "tdd"}, strings.NewReader(""), &out, &errBuf)
|
|
require.NoError(t, err)
|
|
got := out.String()
|
|
assert.Contains(t, got, "tdd")
|
|
assert.Contains(t, got, "47 / 50")
|
|
assert.Contains(t, got, "94%")
|
|
}
|
|
|
|
func TestRunBrainPassRate_NoData(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"skill":"tdd","window":"7d","pass":0,"fail":0,"skip":0,"total":0,"pass_rate":null}`))
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"pass-rate", "tdd"}, strings.NewReader(""), &out, &errBuf)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, out.String(), "no data")
|
|
}
|
|
|
|
func TestRunBrainPassRate_JSON(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"skill":"tdd","window":"7d","pass":47,"fail":3,"skip":0,"total":50,"pass_rate":0.94}`))
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"pass-rate", "--json", "tdd"}, strings.NewReader(""), &out, &errBuf)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, out.String(), `"pass_rate": 0.94`)
|
|
}
|
|
|
|
func TestRunBrainPassRate_MissingSkill(t *testing.T) {
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"pass-rate"}, strings.NewReader(""), &out, &errBuf)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "skill required")
|
|
}
|
|
|
|
func TestRunBrainPassRate_WindowFlag(t *testing.T) {
|
|
gotWindow := ""
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotWindow = r.URL.Query().Get("window")
|
|
_, _ = w.Write([]byte(`{"skill":"tdd","window":"30d","pass":0,"fail":0,"skip":0,"total":0,"pass_rate":null}`))
|
|
}))
|
|
defer srv.Close()
|
|
t.Setenv("BRAIN_URL", srv.URL)
|
|
|
|
var out, errBuf bytes.Buffer
|
|
err := runBrain(context.Background(), []string{"pass-rate", "--window", "30d", "tdd"}, strings.NewReader(""), &out, &errBuf)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "30d", gotWindow)
|
|
}
|