Files
ratatoskr-go/internal/opencode/client_test.go
Hermes e1167c9537
All checks were successful
CI / test (push) Successful in 40s
CI / build-and-package (amd64, linux) (push) Successful in 36s
CI / build-and-package (amd64, windows) (push) Successful in 36s
feat(opencode): v1.18 — перейти с POST /session/:id/prompt на /session/:id/message
Промпт отправляется в /api/session/:id/message (v1.18) с новым форматом
тела parts:[{type:"text"}], вместо {prompt:{text}}. Причина перехода:
/prompt сам подставляет не ту модель, что в конфиге; /message использует
модель из конфига.

- client.go: Send → POST /message + тело parts; шапка-док v1.18
- тесты: fakeAPIServer + e2eFakeAPI отвечают на POST /message (GET уже был)
2026-08-18 18:58:23 +05:00

116 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package opencode
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
)
// fakeAPIServer — минимальный фейк opencode serve HTTP API v1.18.
type fakeAPIServer struct {
messages []sessionMessage
failCreate bool
failVerify bool
}
func (f *fakeAPIServer) handler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/api/session", func(w http.ResponseWriter, r *http.Request) {
if f.failCreate {
http.Error(w, "boom", http.StatusInternalServerError)
return
}
writeJSON(w, map[string]any{"data": map[string]any{"id": "sess-fake"}})
})
mux.HandleFunc("/api/session/{id}/wait", func(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, map[string]any{})
})
mux.HandleFunc("/api/session/{id}/interrupt", func(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, map[string]any{})
})
mux.HandleFunc("/api/session/{id}/message", func(w http.ResponseWriter, r *http.Request) {
// GET — чтение сообщений; POST — отправка промпта (v1.18).
if r.Method == http.MethodGet {
if len(f.messages) == 0 {
writeJSON(w, map[string]any{"data": []sessionMessage{}})
return
}
writeJSON(w, map[string]any{"data": f.messages})
return
}
writeJSON(w, map[string]any{"data": map[string]any{}})
})
return mux
}
func writeJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(v)
}
// textPart — анонимная text-часть assistant-сообщения.
func textPart(s string) struct {
Type string `json:"type"`
Text string `json:"text"`
} {
return struct {
Type string `json:"type"`
Text string `json:"text"`
}{Type: "text", Text: s}
}
func fakeClient(t *testing.T, f *fakeAPIServer) *Client {
t.Helper()
ts := httptest.NewServer(f.handler())
t.Cleanup(ts.Close)
return &Client{BaseURL: ts.URL}
}
func TestClient_CreateSession(t *testing.T) {
c := fakeClient(t, &fakeAPIServer{})
id, err := c.CreateSession(context.Background(), "ratatoskr-analyst")
if err != nil {
t.Fatalf("CreateSession err: %v", err)
}
if id != "sess-fake" {
t.Errorf("id = %q, want sess-fake", id)
}
}
func TestClient_CreateSessionFail(t *testing.T) {
c := fakeClient(t, &fakeAPIServer{failCreate: true})
if _, err := c.CreateSession(context.Background(), "x"); err == nil {
t.Fatal("CreateSession должен упасть при 500, а не nil")
}
}
func TestClient_Verdict(t *testing.T) {
c := fakeClient(t, &fakeAPIServer{
messages: []sessionMessage{{Type: "assistant", Content: []struct {
Type string `json:"type"`
Text string `json:"text"`
}{textPart(`{"phase":"ready"}`)}}},
})
vd, err := c.Verdict(context.Background(), "sess-fake")
if err != nil {
t.Fatalf("Verdict err: %v", err)
}
if vd != `{"phase":"ready"}` {
t.Errorf("verdict = %q, want вердикт модели", vd)
}
}
func TestClient_VerdictNoText(t *testing.T) {
c := fakeClient(t, &fakeAPIServer{}) // нет assistant-сообщения с text
if _, err := c.Verdict(context.Background(), "sess-fake"); err == nil {
t.Fatal("Verdict должен упасть, когда нет text-части")
} else {
var ce *ClientErr
if !errors.As(err, &ce) {
t.Errorf("ожидался *ClientErr, got %T", err)
}
}
}