Files
llmgw/internal/web/connect_test.go
T
Ilker ManapandClaude Opus 5.5 f989e67e1a Connect an editor from the account page, and serve the Responses API
The account page makes a key for Continue, Codex or Cline and shows
what the tool needs with the key, this gateway's address and a served
model in it: Continue's assistant file, written to its own place under
~/.continue/assistants so the person's config.yaml is not touched;
Codex's provider and profile, added to ~/.codex/config.toml without
changing its default model; and for Cline, whose settings live in VS
Code's secret storage, the four fields to paste. Continue and Codex get
a one-line command whose script, served without a session, holds no
secret - the key comes from the environment, never a URL - keeps a copy
of whatever it replaces, and leaves the rest of the file as it was.

POST /v1/responses translates the Responses API, which Codex speaks, to
chat completions and back: instructions, messages, function tools and
Codex's free-form custom tools, their calls and outputs, and usage, as a
response object or as its stream of typed events. It goes down the same
path as any request - quotas, admission, routing, usage. Server-side
tools and stored responses have no equivalent and are ignored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:04:17 +02:00

118 lines
4.2 KiB
Go

package web
import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
func TestEachToolGetsItsConfigurationWithTheKey(t *testing.T) {
c := connectFor("continue", "https://gw.example", "qwen2.5-coder-7b", "sk-llmgw-abc")
if !strings.Contains(c.Files[0].Body, "apiBase: https://gw.example/v1") || !strings.Contains(c.Files[0].Body, "apiKey: sk-llmgw-abc") ||
c.Files[0].Path != "~/.continue/assistants/llmgw.yaml" {
t.Errorf("continue = %+v", c.Files)
}
// The key goes in the environment, not the URL, where it would be kept in
// shell history and proxy logs.
if strings.Contains(strings.Split(c.Command, "|")[0], "sk-llmgw-abc") {
t.Errorf("the key is in the URL: %s", c.Command)
}
x := connectFor("codex", "https://gw.example", "qwen2.5-coder-7b", "sk-llmgw-abc")
if strings.Contains(x.Files[0].Body, "sk-llmgw-abc") || !strings.Contains(x.Files[0].Body, `env_key = "LLMGW_API_KEY"`) {
t.Errorf("codex writes the key into its file: %s", x.Files[0].Body)
}
cl := connectFor("cline", "https://gw.example", "m", "sk-llmgw-abc")
if len(cl.Fields) != 4 || cl.Fields[1].Value != "https://gw.example/v1" || cl.Command != "" {
t.Errorf("cline = %+v", cl)
}
}
// runScript fetches a setup script and runs it with HOME in a temporary
// directory, as a person's shell would.
func runScript(t *testing.T, tool string, env ...string) string {
t.Helper()
p := &AccountPages{}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://gw.example/setup/"+tool+"?model=qwen2.5-coder-7b", nil)
r.SetPathValue("tool", tool)
p.setupScript(w, r)
if w.Code != http.StatusOK {
t.Fatalf("%d %s", w.Code, w.Body.String())
}
home := t.TempDir()
cmd := exec.Command("sh")
cmd.Stdin = strings.NewReader(w.Body.String())
cmd.Env = append([]string{"HOME=" + home, "PATH=" + os.Getenv("PATH")}, env...)
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%v: %s", err, out)
}
return home
}
func TestTheContinueScriptWritesItsOwnFileWithTheKey(t *testing.T) {
home := runScript(t, "continue", "LLMGW_KEY=sk-llmgw-xyz")
b, err := os.ReadFile(filepath.Join(home, ".continue", "assistants", "llmgw.yaml"))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), "apiKey: sk-llmgw-xyz") || strings.Contains(string(b), "__LLMGW_KEY__") {
t.Errorf("wrote %s", b)
}
if fi, _ := os.Stat(filepath.Join(home, ".continue", "assistants", "llmgw.yaml")); fi.Mode().Perm()&0o077 != 0 {
t.Errorf("a file holding a key is readable by others: %v", fi.Mode())
}
}
func TestTheCodexScriptKeepsTheRestOfTheConfig(t *testing.T) {
// Run twice over a config with the person's own settings: theirs stay,
// and ours appear once.
p := &AccountPages{}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://gw.example/setup/codex?model=qwen2.5-coder-7b", nil)
r.SetPathValue("tool", "codex")
p.setupScript(w, r)
home := t.TempDir()
if err := os.MkdirAll(filepath.Join(home, ".codex"), 0o755); err != nil {
t.Fatal(err)
}
mine := "model = \"o3\"\n\n[mcp_servers.docs]\ncommand = \"docs-mcp\"\n"
cfg := filepath.Join(home, ".codex", "config.toml")
if err := os.WriteFile(cfg, []byte(mine), 0o644); err != nil {
t.Fatal(err)
}
for range 2 {
cmd := exec.Command("sh")
cmd.Stdin = strings.NewReader(w.Body.String())
cmd.Env = []string{"HOME=" + home, "PATH=" + os.Getenv("PATH")}
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("%v: %s", err, out)
}
}
b, _ := os.ReadFile(cfg)
got := string(b)
if !strings.Contains(got, `model = "o3"`) || !strings.Contains(got, "[mcp_servers.docs]") {
t.Errorf("the person's settings were lost:\n%s", got)
}
if strings.Count(got, "[model_providers.llmgw]") != 1 || strings.Count(got, "[profiles.llmgw]") != 1 {
t.Errorf("ours should be there once:\n%s", got)
}
if backups, _ := filepath.Glob(cfg + ".bak.*"); len(backups) == 0 {
t.Error("no copy was kept of the file as it was")
}
}
func TestASetupScriptRefusesANameAShellWouldRead(t *testing.T) {
p := &AccountPages{}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://gw.example/setup/codex?model=$(rm%20-rf%20~)", nil)
r.SetPathValue("tool", "codex")
p.setupScript(w, r)
if w.Code != http.StatusBadRequest {
t.Errorf("%d", w.Code)
}
}