Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

199 lines
8.5 KiB
Go

//go:build evalingest
package server_test
import (
"context"
"fmt"
"net/http"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/config"
"go.kenn.io/agentsview/internal/db"
)
// evalQueryResponse mirrors the fields of the (unexported) recall query
// response that these tests assert on.
type evalQueryResponse struct {
RecallEntries []db.RecallResult `json:"entries"`
TrustedOnly bool `json:"trusted_only"`
Context string `json:"context"`
}
type evalIngestResponse struct {
RunID string `json:"run_id"`
TrajectoryID string `json:"trajectory_id"`
CorpusID string `json:"corpus_id"`
EntriesIndexed int `json:"entries_indexed"`
}
func TestIngestEvalTrajectoryEndToEnd(t *testing.T) {
te := setup(t)
w := te.post(t, "/api/v1/recall/eval/trajectories", `
{"run_id":"run-a","trajectory_id":"traj-a","extractor_method":"eval-harness-raw-trajectory","source_version":"test-harness-v1","trajectory":{"question":"where did zaphod hide the towel","answer":"in the bathroom locker"}}
`)
assertStatus(t, w, http.StatusOK)
got := decode[db.EvalTrajectoryIngestResult](t, w)
assert.Equal(t, "run-a", got.RunID)
assert.Equal(t, "traj-a", got.TrajectoryID)
assert.Equal(t, 1, got.EntriesIndexed)
// Retrieve through the query endpoint, scoped by run + extractor method.
q := te.post(t, "/api/v1/recall/query", `
{"query":"zaphod towel","source_run_id":"run-a","extractor_method":"eval-harness-raw-trajectory","trusted_only":false,"limit":10,"include_context":true}
`)
assertStatus(t, q, http.StatusOK)
resp := decode[evalQueryResponse](t, q)
assert.False(t, resp.TrustedOnly)
require.NotEmpty(t, resp.RecallEntries, "ingested chunk should be retrievable")
m := resp.RecallEntries[0]
assert.Equal(t, "fact", m.Type)
assert.Equal(t, "eval-harness-raw-trajectory", m.ExtractorMethod)
assert.Equal(t, "run-a", m.SourceRunID)
assert.Contains(t, m.SourceEpisodeID, ":chunk:")
assert.True(t, m.Transferable)
assert.True(t, m.ProvenanceOK)
assert.Contains(t, resp.Context, "towel")
// Raw eval rows are deliberately quarantined from trusted recall even when
// transferability and provenance are otherwise true.
trusted := te.post(t, "/api/v1/recall/query", `
{"query":"zaphod towel","source_run_id":"run-a","extractor_method":"eval-harness-raw-trajectory","trusted_only":true,"limit":10,"include_context":true}
`)
assertStatus(t, trusted, http.StatusOK)
trustedResp := decode[evalQueryResponse](t, trusted)
assert.True(t, trustedResp.TrustedOnly)
assert.Empty(t, trustedResp.RecallEntries)
// The placeholder session exists, marked as an eval session.
sess, err := te.db.GetSession(context.Background(), m.SourceSessionID)
require.NoError(t, err)
require.NotNil(t, sess)
assert.Equal(t, "test-harness-v1", sess.SourceVersion)
}
func TestIngestEvalTrajectoryIdempotent(t *testing.T) {
te := setup(t)
body := `
{"run_id":"run-b","trajectory_id":"traj-b","extractor_method":"eval-harness-raw-trajectory","source_version":"test-harness-v1","trajectory":{"text":"hello there general"}}
`
w1 := te.post(t, "/api/v1/recall/eval/trajectories", body)
assertStatus(t, w1, http.StatusOK)
assert.Equal(t, 1, decode[db.EvalTrajectoryIngestResult](t, w1).EntriesIndexed)
w2 := te.post(t, "/api/v1/recall/eval/trajectories", body)
assertStatus(t, w2, http.StatusOK)
assert.Equal(t, 0, decode[db.EvalTrajectoryIngestResult](t, w2).EntriesIndexed)
}
func TestIngestEvalTrajectoryVersionsExposeQueryableCorpusID(t *testing.T) {
te := setup(t)
first := decode[evalIngestResponse](t, te.post(
t,
"/api/v1/recall/eval/trajectories",
`{"run_id":"run-versioned","trajectory_id":"traj-versioned","extractor_method":"eval-harness-raw-trajectory","source_version":"harness-v1","trajectory":{"text":"alpha memory"}}`,
))
require.NotEmpty(t, first.CorpusID)
assert.Equal(t, 1, first.EntriesIndexed)
second := decode[evalIngestResponse](t, te.post(
t,
"/api/v1/recall/eval/trajectories",
`{"run_id":"run-versioned","trajectory_id":"traj-versioned","extractor_method":"eval-harness-raw-trajectory","source_version":"harness-v2","trajectory":{"text":"beta memory"}}`,
))
require.NotEmpty(t, second.CorpusID)
assert.NotEqual(t, first.CorpusID, second.CorpusID)
assert.Equal(t, 1, second.EntriesIndexed)
queryCorpus := func(corpusID string) evalQueryResponse {
body := fmt.Sprintf(
`{"query":"memory","source_run_id":"run-versioned","extractor_method":"eval-harness-raw-trajectory","source_session_id":"%s","trusted_only":false,"limit":10}`,
corpusID,
)
w := te.post(t, "/api/v1/recall/query", body)
assertStatus(t, w, http.StatusOK)
return decode[evalQueryResponse](t, w)
}
firstCorpus := queryCorpus(first.CorpusID)
require.Len(t, firstCorpus.RecallEntries, 1)
assert.Equal(t, "alpha memory", firstCorpus.RecallEntries[0].Body)
secondCorpus := queryCorpus(second.CorpusID)
require.Len(t, secondCorpus.RecallEntries, 1)
assert.Equal(t, "beta memory", secondCorpus.RecallEntries[0].Body)
}
func TestIngestEvalTrajectoryRefusesDefaultDataDir(t *testing.T) {
home := t.TempDir()
defaultDataDir := filepath.Join(home, ".agentsview")
te := setup(t, func(c *config.Config) {
c.DataDir = defaultDataDir
c.DBPath = filepath.Join(defaultDataDir, "test.db")
})
w := te.post(t, "/api/v1/recall/eval/trajectories", `
{"run_id":"run-c","trajectory_id":"traj-c","extractor_method":"eval-harness-raw-trajectory","source_version":"test-harness-v1","trajectory":{"text":"hi"}}
`)
assertStatus(t, w, http.StatusForbidden)
assertBodyContains(t, w, "default agentsview data directory")
assertBodyContains(t, w, "allow_production_import")
}
func TestIngestEvalTrajectoryAllowsDefaultDataDirWithOverride(t *testing.T) {
home := t.TempDir()
defaultDataDir := filepath.Join(home, ".agentsview")
te := setup(t, func(c *config.Config) {
c.DataDir = defaultDataDir
c.DBPath = filepath.Join(defaultDataDir, "test.db")
})
w := te.post(t, "/api/v1/recall/eval/trajectories?allow_production_import=true", `
{"run_id":"run-c","trajectory_id":"traj-c","extractor_method":"eval-harness-raw-trajectory","source_version":"test-harness-v1","trajectory":{"text":"hi"}}
`)
assertStatus(t, w, http.StatusOK)
assert.Equal(t, 1, decode[db.EvalTrajectoryIngestResult](t, w).EntriesIndexed)
}
func TestIngestEvalTrajectoryEmptyObjectIndexesNothing(t *testing.T) {
te := setup(t)
w := te.post(t, "/api/v1/recall/eval/trajectories",
`{"run_id":"r","trajectory_id":"t","extractor_method":"eval-harness-raw-trajectory","source_version":"test-harness-v1","trajectory":{"n":1}}`)
assertStatus(t, w, http.StatusOK)
assert.Equal(t, 0, decode[db.EvalTrajectoryIngestResult](t, w).EntriesIndexed)
}
func TestIngestEvalTrajectoryValidation(t *testing.T) {
te := setup(t)
const path = "/api/v1/recall/eval/trajectories"
const validFields = `"extractor_method":"eval-harness-raw-trajectory","source_version":"test-harness-v1"`
tooLong := strings.Repeat("x", 201)
cases := []struct {
name string
path string
body string
code int
}{
{"missing run_id", path, `{"trajectory_id":"t",` + validFields + `,"trajectory":{"text":"x"}}`, http.StatusBadRequest},
{"missing trajectory_id", path, `{"run_id":"r",` + validFields + `,"trajectory":{"text":"x"}}`, http.StatusBadRequest},
{"missing extractor_method", path, `{"run_id":"r","trajectory_id":"t","source_version":"test-harness-v1","trajectory":{"text":"x"}}`, http.StatusBadRequest},
{"missing source_version", path, `{"run_id":"r","trajectory_id":"t","extractor_method":"eval-harness-raw-trajectory","trajectory":{"text":"x"}}`, http.StatusBadRequest},
{"extractor_method too long", path, `{"run_id":"r","trajectory_id":"t","extractor_method":"` + tooLong + `","source_version":"test-harness-v1","trajectory":{"text":"x"}}`, http.StatusBadRequest},
{"source_version too long", path, `{"run_id":"r","trajectory_id":"t","extractor_method":"eval-harness-raw-trajectory","source_version":"` + tooLong + `","trajectory":{"text":"x"}}`, http.StatusBadRequest},
{"missing trajectory", path, `{"run_id":"r","trajectory_id":"t",` + validFields + `}`, http.StatusBadRequest},
{"null trajectory", path, `{"run_id":"r","trajectory_id":"t",` + validFields + `,"trajectory":null}`, http.StatusBadRequest},
{"invalid json", path, `{not json`, http.StatusBadRequest},
{"invalid override param", path + "?allow_production_import=1", `{"run_id":"r","trajectory_id":"t",` + validFields + `,"trajectory":{"text":"x"}}`, http.StatusBadRequest},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
w := te.post(t, tc.path, tc.body)
assertStatus(t, w, tc.code)
})
}
}