feat: model selection at spawn (POST /api/spawn model -> LVMH_MODEL -> bridge createAgentSession); ops prepare resets ops session (fresh context per prepare)
This commit is contained in:
@@ -4,8 +4,12 @@ package main
|
||||
// GET /api/repos built flags, POST /api/spawn imageUsed resolution.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -14,17 +18,46 @@ import (
|
||||
// TestAPIPrepareRepoRoutesToOps: with a live ops agent WS the prepare route
|
||||
// delivers the prompt; without it the route 409s.
|
||||
func TestAPIPrepareRepoRoutesToOps(t *testing.T) {
|
||||
ts, _ := newTestServer(t)
|
||||
useFakeGit(t, fakeGitModeOK)
|
||||
f := newFakeDocker()
|
||||
tsD := f.server(t)
|
||||
t.Setenv("DOCKER_HOST", "tcp://"+tsD.Listener.Addr().String())
|
||||
buildCtx := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(buildCtx, "docker"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(buildCtx, "docker", "worker.Dockerfile"), []byte("FROM scratch\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv(envWorkerDockerfile, filepath.Join(buildCtx, "docker", "worker.Dockerfile"))
|
||||
t.Setenv(envWorkerContext, buildCtx)
|
||||
t.Setenv(envRepoDir, t.TempDir())
|
||||
t.Setenv(envControlDockerfile, filepath.Join(buildCtx, "docker", "control.Dockerfile"))
|
||||
if err := os.WriteFile(filepath.Join(buildCtx, "docker", "control.Dockerfile"), []byte("FROM scratch\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("LVMH_TEST_OPS_BOOT_WAIT", "3s")
|
||||
|
||||
daemonToken = testToken
|
||||
store := openTestStore(t)
|
||||
hub := NewHub(store)
|
||||
sp, err := NewSpawner(context.Background(), store, hub, "https://gitlab.example/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
srv := &Server{store: store, hub: hub, spawn: sp, gitlab: NewGitLab(store, "https://gitlab.example")}
|
||||
ts := httptest.NewServer(srv.Routes(""))
|
||||
t.Cleanup(ts.Close)
|
||||
auth := testToken
|
||||
prepareURL := ts.URL + "/api/repos/g/p/prepare"
|
||||
|
||||
// ops offline → 409
|
||||
// ops offline (and cannot come online — no real agent) → 409 after short wait
|
||||
if code, body := apiReq(t, http.MethodPost, prepareURL, auth, ""); code != http.StatusConflict {
|
||||
t.Fatalf("prepare offline = %d %s, want 409", code, body)
|
||||
}
|
||||
|
||||
// path validation
|
||||
for _, path := range []string{"/api/repos/noslash/prepare", "/api/repos/g/p/notprepare", "/api/repos/g/p/image"} {
|
||||
for _, path := range []string{"/api/repos/noslash/prepare", "/api/repos/g/p/notprepare"} {
|
||||
if code, _ := apiReq(t, http.MethodPost, ts.URL+path, auth, ""); code != http.StatusBadRequest {
|
||||
t.Fatalf("POST %s = %d, want 400", path, code)
|
||||
}
|
||||
@@ -33,7 +66,14 @@ func TestAPIPrepareRepoRoutesToOps(t *testing.T) {
|
||||
t.Fatal("prepare must require auth")
|
||||
}
|
||||
|
||||
// %2F-encoded repo path (web encodeURIComponent) decodes to the same route
|
||||
// online ops: prompt delivered on the ops session WS; the reset removes
|
||||
// the ops container + wipes its transcript first.
|
||||
if err := store.UpsertSession(SessionInfo{ID: opsSessionID}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := store.AppendEvent(Event{SessionID: opsSessionID, Seq: 1, TS: 1, Type: "message_end", Payload: json.RawMessage(`{}`)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ws := dialAgent(t, ts)
|
||||
if err := ws.WriteJSON(helloFrame(opsSessionID)); err != nil {
|
||||
t.Fatalf("hello: %v", err)
|
||||
@@ -49,17 +89,20 @@ func TestAPIPrepareRepoRoutesToOps(t *testing.T) {
|
||||
if prompt["type"] != evPrompt {
|
||||
t.Fatalf("prompt frame = %v", prompt)
|
||||
}
|
||||
if prompt["sessionId"] != opsSessionID {
|
||||
t.Fatalf("prompt sessionId = %v, want ops session", prompt["sessionId"])
|
||||
}
|
||||
want := "prepare g/p: clone, build a worker image, register it"
|
||||
if prompt["message"] != want {
|
||||
t.Fatalf("prompt message = %q, want %q", prompt["message"], want)
|
||||
}
|
||||
// transcript wiped by the reset
|
||||
evs, err := store.EventsAfter(opsSessionID, 0, 100)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(evs) != 0 {
|
||||
t.Fatalf("ops transcript not cleared: %d events", len(evs))
|
||||
}
|
||||
}
|
||||
|
||||
// TestAPIRepoImagesBuiltFlag: each row carries built=true when the image
|
||||
// exists on the docker host, false when registered but missing.
|
||||
func TestAPIRepoImagesBuiltFlag(t *testing.T) {
|
||||
ts, f := newSpawnAPIServer(t)
|
||||
auth := testToken
|
||||
|
||||
Reference in New Issue
Block a user