feat(hyperguild): brain write subcommand
Reads markdown from stdin, POSTs to the brain's /write endpoint with type + slug, prints the resulting path. Pairs with 'brain query' for shell-friendly read/write access to the brain HTTP REST API. Tests cover success, missing args, backend error propagation, and empty stdin (which produces an empty content payload — the brain server's responsibility to validate).
This commit is contained in:
@@ -52,8 +52,22 @@ func runBrainQuery(ctx context.Context, args []string, _ io.Reader, stdout, stde
|
||||
return nil
|
||||
}
|
||||
|
||||
// runBrainWrite is implemented in Task 5; stub now returns an explicit error
|
||||
// so the router compiles and tests for runBrainQuery can run.
|
||||
func runBrainWrite(_ context.Context, _ []string, _ io.Reader, _, _ io.Writer) error {
|
||||
return errors.New("brain write: not implemented (Task 5)")
|
||||
func runBrainWrite(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) error {
|
||||
fs := flag.NewFlagSet("brain write", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return fmt.Errorf("parse flags: %w", err)
|
||||
}
|
||||
if fs.NArg() < 2 {
|
||||
return errors.New("brain write: type and slug required (e.g. brain write knowledge my-slug)")
|
||||
}
|
||||
kind := fs.Arg(0)
|
||||
slug := fs.Arg(1)
|
||||
|
||||
res, err := newBrainClient().Write(ctx, kind, slug, stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(stdout, res.Path) //nolint:errcheck
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -79,3 +81,70 @@ func TestRunBrain_UnknownSubsubcommand(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user