Files
multica/server/pkg/agent/omp_test.go
T
603519d158 MUL-6977: test: remove implementation-only coverage (#7932)
* test: remove implementation-only coverage

Co-authored-by: multica-agent <[email protected]>

* test: remove remaining implementation-only coverage

Co-authored-by: multica-agent <[email protected]>

* test: restore compatibility and fragment navigation coverage

Co-authored-by: multica-agent <[email protected]>

* test: preserve behavioral cleanup contracts

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-03 13:23:25 +08:00

485 lines
18 KiB
Go

package agent
import (
"context"
"log/slog"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
// TestOmpNewDispatchesToPiBackend asserts that ResolveBackend("omp") dispatches to the
// pi backend via the descriptor registry — the core contract that omp is a
// runtime identity on the pi protocol, not a separate protocol family.
// IsSupportedType("omp") is false (it's not a protocol family), but New
// resolves it through BuiltinRuntimes to the pi backend with the correct
// executable and label overrides.
func TestOmpNewDispatchesToPiBackend(t *testing.T) {
if IsSupportedType("omp") {
t.Errorf("omp must not be in SupportedTypes (it is a runtime identity, not a protocol family)")
}
b, err := ResolveBackend("omp", Config{Logger: slog.Default()})
if err != nil {
t.Fatalf("New(omp) returned error: %v", err)
}
pb, ok := b.(*piBackend)
if !ok {
t.Fatalf("New(omp) returned %T, want *piBackend", b)
}
if pb.defaultExecutable != "omp" {
t.Errorf("defaultExecutable = %q, want %q", pb.defaultExecutable, "omp")
}
if pb.providerLabel != "omp" {
t.Errorf("providerLabel = %q, want %q", pb.providerLabel, "omp")
}
}
// TestNewRuntimeIsSeparateEntryPoint verifies that NewRuntime (the typed
// entry point for runtime identities) produces the same backend as New()
// when called with "omp" — both should dispatch through the descriptor.
func TestNewRuntimeIsSeparateEntryPoint(t *testing.T) {
b, err := NewRuntime("omp", Config{Logger: slog.Default()})
if err != nil {
t.Fatalf("NewRuntime(omp): %v", err)
}
pb, ok := b.(*piBackend)
if !ok {
t.Fatalf("NewRuntime(omp) returned %T, want *piBackend", b)
}
if pb.defaultExecutable != "omp" {
t.Errorf("defaultExecutable = %q, want %q", pb.defaultExecutable, "omp")
}
if pb.providerLabel != "omp" {
t.Errorf("providerLabel = %q, want %q", pb.providerLabel, "omp")
}
}
// TestNewRuntimeRejectsUnknownID verifies that NewRuntime fails for an
// unregistered runtime identity.
func TestNewRuntimeRejectsUnknownID(t *testing.T) {
if _, err := NewRuntime("definitely-not-a-runtime", Config{}); err == nil {
t.Fatal("expected error for unknown runtime identity")
}
}
// TestNewRejectsRuntimeID verifies that New() (the protocol-family factory)
// rejects a runtime identity like "omp". This pins the contract that New()
// means exactly one thing: family-only. The daemon calls ResolveBackend,
// which routes runtime identities through NewRuntime.
func TestNewRejectsRuntimeID(t *testing.T) {
if _, err := New("omp", Config{Logger: slog.Default()}); err == nil {
t.Fatal("New(\"omp\") should reject runtime identities (it is family-only)")
}
}
// TestOmpExecuteDefaultsToOmpBinary verifies that ResolveBackend("omp", Config{}) with
// an empty ExecutablePath resolves to the "omp" binary name (not "pi") when
// the daemon hasn't pinned a path. This is the contract the daemon relies on
// when it constructs a backend from a probe result that carries no path.
func TestOmpExecuteDefaultsToOmpBinary(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("shell-script fixture is POSIX-only")
}
fakeDir := t.TempDir()
fakePath := filepath.Join(fakeDir, "omp")
script := "#!/bin/sh\n" +
"cat > /dev/null\n" +
"printf '%s\\n' '{\"type\":\"agent_start\"}'\n" +
"printf '%s\\n' '{\"type\":\"turn_end\",\"message\":{\"role\":\"assistant\",\"model\":\"test\",\"usage\":{\"input\":1,\"output\":1}}}'\n" +
"exit 0\n"
writeTestExecutable(t, fakePath, []byte(script))
t.Setenv("PATH", fakeDir)
backend, err := ResolveBackend("omp", Config{Logger: slog.Default()})
if err != nil {
t.Fatalf("New(omp): %v", err)
}
sessionPath := filepath.Join(t.TempDir(), "session.jsonl")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
session, err := backend.Execute(ctx, "test prompt", ExecOptions{
Timeout: 5 * time.Second,
ResumeSessionID: sessionPath,
})
if err != nil {
t.Fatalf("Execute: %v", err)
}
go func() {
for range session.Messages {
}
}()
select {
case result := <-session.Result:
if result.Status != "completed" {
t.Fatalf("expected completed, got %q (error=%q)", result.Status, result.Error)
}
case <-time.After(10 * time.Second):
t.Fatal("timeout waiting for result")
}
}
// TestOmpExecuteRejectsEmptyPrompt verifies the error message uses the "omp"
// label, not "pi". This is the user-facing contract: an omp agent that fails
// must say "omp prompt must not be empty", not "pi prompt must not be empty".
func TestOmpExecuteRejectsEmptyPrompt(t *testing.T) {
t.Parallel()
backend, err := ResolveBackend("omp", Config{ExecutablePath: "/does/not/need/to/exist", Logger: slog.Default()})
if err != nil {
t.Fatalf("New(omp): %v", err)
}
if _, err := backend.Execute(t.Context(), " \n\t ", ExecOptions{}); err == nil {
t.Fatalf("expected empty-prompt error")
} else {
if !strings.Contains(err.Error(), "omp prompt must not be empty") {
t.Fatalf("error = %q, want it to contain %q", err.Error(), "omp prompt must not be empty")
}
if strings.Contains(err.Error(), "pi prompt") {
t.Fatalf("error should not contain hardcoded 'pi prompt': %q", err.Error())
}
}
}
// TestOmpExecuteLabelsErrorsAsOmp verifies that a binary-not-found error uses
// the "omp" label, not "pi".
func TestOmpExecuteLabelsErrorsAsOmp(t *testing.T) {
t.Parallel()
backend, err := ResolveBackend("omp", Config{ExecutablePath: "/nonexistent/omp-binary", Logger: slog.Default()})
if err != nil {
t.Fatalf("New(omp): %v", err)
}
_, err = backend.Execute(t.Context(), "prompt", ExecOptions{})
if err == nil {
t.Fatal("expected error for nonexistent binary")
}
if !strings.Contains(err.Error(), "omp executable not found") {
t.Fatalf("error = %q, want it to contain %q", err.Error(), "omp executable not found")
}
if strings.Contains(err.Error(), "pi executable") {
t.Fatalf("error should not say 'pi executable': %q", err.Error())
}
}
// TestOmpExecuteCompletesFromEventStream verifies that an omp event stream
// (same JSON protocol as pi) drives a task to completion through the pi
// backend. This is the end-to-end protocol compatibility contract.
func TestOmpExecuteCompletesFromEventStream(t *testing.T) {
t.Parallel()
if runtime.GOOS == "windows" {
t.Skip("shell-script fixture is POSIX-only")
}
events := []string{
`{"type":"agent_start"}`,
`{"type":"turn_start"}`,
`{"type":"message_update","assistantMessageEvent":{"type":"text_delta","delta":"hello from omp"}}`,
`{"type":"turn_end","message":{"role":"assistant","model":"omp-test","usage":{"input":10,"output":5}}}`,
}
fakePath := filepath.Join(t.TempDir(), "omp")
writeTestExecutable(t, fakePath, []byte(piEventStreamScript(events)))
backend, err := ResolveBackend("omp", Config{ExecutablePath: fakePath, Logger: slog.Default()})
if err != nil {
t.Fatalf("New(omp): %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
session, err := backend.Execute(ctx, "prompt-ignored", ExecOptions{Timeout: 5 * time.Second})
if err != nil {
t.Fatalf("execute: %v", err)
}
go func() {
for range session.Messages {
}
}()
select {
case result := <-session.Result:
if result.Status != "completed" {
t.Fatalf("expected completed, got %q (error=%q)", result.Status, result.Error)
}
if result.Output != "hello from omp" {
t.Fatalf("Output = %q, want %q", result.Output, "hello from omp")
}
if len(result.Usage) != 1 {
t.Fatalf("expected 1 usage entry, got %d", len(result.Usage))
}
u, ok := result.Usage["omp-test"]
if !ok {
t.Fatalf("expected usage for model %q, got %v", "omp-test", result.Usage)
}
if u.InputTokens != 10 || u.OutputTokens != 5 {
t.Fatalf("usage: input=%d output=%d, want input=10 output=5", u.InputTokens, u.OutputTokens)
}
case <-time.After(10 * time.Second):
t.Fatal("timeout waiting for result")
}
}
// TestParseOmpModels parses a real `omp models --json` output. omp emits
// an object wrapper {"models":[...]} where each entry has separate `provider`,
// `id`, `selector` (provider/id), and `name` fields. The persistable Model.ID
// is the selector (provider/id), matching the convention parsePiModels uses
// so buildPiArgs can hand the whole selector to --model.
func TestParseOmpModels(t *testing.T) {
sample := `{"models":[` +
`{"provider":"anthropic","id":"claude-sonnet-5","selector":"anthropic/claude-sonnet-5","name":"Claude Sonnet 5","contextWindow":200000,"maxTokens":64000,"reasoning":true},` +
`{"provider":"openai","id":"gpt-5","selector":"openai/gpt-5","name":"GPT-5"},` +
`{"provider":"","id":"local-model","selector":"local-model","name":"Local Model"}` +
`]}`
models, err := parseOmpModels([]byte(sample))
if err != nil {
t.Fatalf("parseOmpModels: %v", err)
}
if len(models) != 3 {
t.Fatalf("expected 3 models, got %d", len(models))
}
// Model.ID is the selector (provider/id), NOT the bare id.
if models[0].ID != "anthropic/claude-sonnet-5" || models[0].Provider != "anthropic" || models[0].Label != "Claude Sonnet 5" {
t.Errorf("models[0] = %+v", models[0])
}
if models[1].ID != "openai/gpt-5" || models[1].Provider != "openai" || models[1].Label != "GPT-5" {
t.Errorf("models[1] = %+v", models[1])
}
// Bare model id with empty provider: selector is the bare id.
if models[2].ID != "local-model" || models[2].Provider != "" || models[2].Label != "Local Model" {
t.Errorf("models[2] = %+v", models[2])
}
}
// TestParseOmpModelsEmptyCatalog verifies an empty {"models":[]} wrapper
// degrades gracefully to an empty model list (not an error). Uses the real
// wrapper shape, not a bare top-level array.
func TestParseOmpModelsEmptyCatalog(t *testing.T) {
models, err := parseOmpModels([]byte(`{"models":[]}`))
if err != nil {
t.Fatalf("parseOmpModels({\"models\":[]}): %v", err)
}
if len(models) != 0 {
t.Fatalf("expected 0 models, got %d", len(models))
}
}
// TestParseOmpModelsInvalidJSON verifies a non-JSON output (e.g. usage text
// from a flag mismatch or an old omp that doesn't support --json) degrades
// to an empty list, not a panic.
func TestParseOmpModelsInvalidJSON(t *testing.T) {
models, err := parseOmpModels([]byte("Error: unknown flag --json\nRun `omp --help` for usage."))
if err != nil {
t.Fatalf("parseOmpModels(invalid): %v", err)
}
if len(models) != 0 {
t.Fatalf("expected 0 models for invalid JSON, got %d", len(models))
}
}
// TestParseOmpModelsDeduplicates verifies that duplicate model selectors are
// collapsed, matching the pi discovery behaviour.
func TestParseOmpModelsDeduplicates(t *testing.T) {
sample := `{"models":[` +
`{"provider":"anthropic","id":"claude-sonnet-5","selector":"anthropic/claude-sonnet-5","name":"Sonnet"},` +
`{"provider":"anthropic","id":"claude-sonnet-5","selector":"anthropic/claude-sonnet-5","name":"Sonnet Dup"}` +
`]}`
models, _ := parseOmpModels([]byte(sample))
if len(models) != 1 {
t.Fatalf("expected 1 model (deduplicated), got %d", len(models))
}
}
// TestOmpModelsJSONShape verifies that parseOmpModels correctly parses the
// real omp models --json output shape: {"models":[{...}]} with provider/id
// as separate fields, and that Model.ID is the selector.
func TestOmpModelsJSONShape(t *testing.T) {
raw := `{"models":[{"provider":"zai","id":"glm-4.6","selector":"zai/glm-4.6","name":"GLM 4.6","contextWindow":128000,"maxTokens":4096},{"provider":"xai","id":"grok-4-fast","selector":"xai/grok-4-fast","name":"Grok 4 Fast"}]}`
models, err := parseOmpModels([]byte(raw))
if err != nil {
t.Fatalf("parseOmpModels: %v", err)
}
if len(models) != 2 {
t.Fatalf("expected 2 models, got %d", len(models))
}
// Model.ID is the selector, not the bare id.
if models[0].ID != "zai/glm-4.6" || models[0].Provider != "zai" || models[0].Label != "GLM 4.6" {
t.Errorf("models[0] = %+v", models[0])
}
if models[1].ID != "xai/grok-4-fast" || models[1].Provider != "xai" || models[1].Label != "Grok 4 Fast" {
t.Errorf("models[1] = %+v", models[1])
}
}
// TestOmpSelectorSurvivesToBuildPiArgs is the regression test the review
// asked for: a real `omp models --json` fixture → parseOmpModels →
// buildPiArgs should hand the selector to --model whole. This pins the
// contract that Model.ID is the selector (provider/id) and that the selector
// reaches the CLI intact — the pi-family resolver takes it from there.
func TestOmpSelectorSurvivesToBuildPiArgs(t *testing.T) {
raw := `{"models":[{"provider":"anthropic","id":"claude-sonnet-5","selector":"anthropic/claude-sonnet-5","name":"Claude Sonnet 5"}]}`
models, err := parseOmpModels([]byte(raw))
if err != nil {
t.Fatalf("parseOmpModels: %v", err)
}
if len(models) != 1 {
t.Fatalf("expected 1 model, got %d", len(models))
}
// The model ID is "anthropic/claude-sonnet-5" (the selector), and that is
// exactly what --model receives. Splitting it into --provider anthropic
// --model claude-sonnet-5 resolves identically for a real provider prefix
// but breaks slash-shaped model ids, so the split is gone (GH #7300).
args := buildPiArgs("/tmp/session.jsonl", ExecOptions{Model: models[0].ID}, slog.Default())
joined := strings.Join(args, " ")
if !strings.Contains(joined, "--model anthropic/claude-sonnet-5") {
t.Errorf("args missing --model anthropic/claude-sonnet-5: %s", joined)
}
if strings.Contains(joined, "--provider") {
t.Errorf("args should not synthesize --provider: %s", joined)
}
}
// TestOmpAndPiCanCoexist verifies that both "pi" and "omp" can be constructed
// and used side by side — the core contract the issue (#3989) asks for.
func TestOmpAndPiCanCoexist(t *testing.T) {
t.Parallel()
piBe, err := New("pi", Config{ExecutablePath: "/fake/pi", Logger: slog.Default()})
if err != nil {
t.Fatalf("New(pi): %v", err)
}
ompBe, err := ResolveBackend("omp", Config{ExecutablePath: "/fake/omp", Logger: slog.Default()})
if err != nil {
t.Fatalf("New(omp): %v", err)
}
if _, ok := piBe.(*piBackend); !ok {
t.Fatalf("pi backend is %T, want *piBackend", piBe)
}
if _, ok := ompBe.(*piBackend); !ok {
t.Fatalf("omp backend is %T, want *piBackend", ompBe)
}
pb := piBe.(*piBackend)
ob := ompBe.(*piBackend)
if pb.defaultExecutable != "" {
t.Errorf("pi defaultExecutable = %q, want empty", pb.defaultExecutable)
}
if ob.defaultExecutable != "omp" {
t.Errorf("omp defaultExecutable = %q, want %q", ob.defaultExecutable, "omp")
}
if pb.providerLabel != "" {
t.Errorf("pi providerLabel = %q, want empty", pb.providerLabel)
}
if ob.providerLabel != "omp" {
t.Errorf("omp providerLabel = %q, want %q", ob.providerLabel, "omp")
}
}
// TestDiscoverOmpModelsNonZeroExit verifies that discoverOmpModels returns
// an empty catalog when the omp binary exits non-zero (e.g. an old omp that
// doesn't support `models --json` and prints usage to stderr). This is the
// fake-executable integration test the review asked for.
func TestDiscoverOmpModelsNonZeroExit(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("shell-script fixture is POSIX-only")
}
fakePath := filepath.Join(t.TempDir(), "omp")
script := "#!/bin/sh\n" +
"echo 'Error: unknown command \"models\"' >&2\n" +
"echo 'Run `omp --help` for usage.' >&2\n" +
"exit 1\n"
writeTestExecutable(t, fakePath, []byte(script))
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
models, err := discoverOmpModels(ctx, Command{Path: fakePath})
if err != nil {
t.Fatalf("discoverOmpModels: %v", err)
}
if len(models) != 0 {
t.Fatalf("expected 0 models for non-zero-exit omp, got %d", len(models))
}
}
// TestDiscoverOmpModelsMissingBinary verifies that a missing omp binary
// degrades to an empty catalog, not an error.
func TestDiscoverOmpModelsMissingBinary(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
models, err := discoverOmpModels(ctx, Command{Path: "/nonexistent/omp-binary"})
if err != nil {
t.Fatalf("discoverOmpModels: %v", err)
}
if len(models) != 0 {
t.Fatalf("expected 0 models for missing binary, got %d", len(models))
}
}
// TestOmpAndPiRegisterSideBySide is the daemon-level registration test the
// review asked for: with both binaries present, probing should discover two
// runtimes with the right commands. This exercises the probe path (not just
// backend construction), verifying that the descriptor-driven probe loop in
// agents_probe.go finds both "pi" and "omp" independently.
func TestOmpAndPiRegisterSideBySide(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("shell-script fixture is POSIX-only")
}
fakeDir := t.TempDir()
// Create fake pi and omp binaries.
for _, name := range []string{"pi", "omp"} {
fakePath := filepath.Join(fakeDir, name)
script := "#!/bin/sh\nexit 0\n"
writeTestExecutable(t, fakePath, []byte(script))
}
t.Setenv("PATH", fakeDir)
// Probe both CLIs the way the daemon would — using the same env vars
// the descriptor-driven probe loop uses.
for _, tc := range []struct {
envPath string
envModel string
cmd string
id string
}{
{"MULTICA_PI_PATH", "MULTICA_PI_MODEL", "pi", "pi"},
{"MULTICA_OMP_PATH", "MULTICA_OMP_MODEL", "omp", "omp"},
} {
t.Run(tc.id, func(t *testing.T) {
// Verify the descriptor's env prefix and command match what
// the probe loop would use.
desc, ok := BuiltinRuntimeByID(tc.id)
if tc.id == "omp" {
if !ok {
t.Fatalf("BuiltinRuntimeByID(%q) not found", tc.id)
}
if desc.EnvPrefix != tc.envPath[:len(tc.envPath)-5] {
t.Errorf("descriptor EnvPrefix = %q, want %q", desc.EnvPrefix, tc.envPath[:len(tc.envPath)-5])
}
if desc.DefaultCommand != tc.cmd {
t.Errorf("descriptor DefaultCommand = %q, want %q", desc.DefaultCommand, tc.cmd)
}
}
// The backend should construct successfully.
b, err := ResolveBackend(tc.id, Config{ExecutablePath: filepath.Join(fakeDir, tc.cmd), Logger: slog.Default()})
if err != nil {
t.Fatalf("New(%q): %v", tc.id, err)
}
pb, ok := b.(*piBackend)
if !ok {
t.Fatalf("New(%q) = %T, want *piBackend", tc.id, b)
}
if tc.id == "omp" {
if pb.defaultExecutable != "omp" {
t.Errorf("defaultExecutable = %q, want %q", pb.defaultExecutable, "omp")
}
if pb.providerLabel != "omp" {
t.Errorf("providerLabel = %q, want %q", pb.providerLabel, "omp")
}
}
})
}
}