586 lines
18 KiB
Go
586 lines
18 KiB
Go
package logstore
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// setupPerfTestDB connects to Postgres, runs migrations, and returns the store.
|
|
func setupPerfTestDB(t *testing.T) (*RDBLogStore, *gorm.DB) {
|
|
t.Helper()
|
|
db := trySetupPostgresDB(t)
|
|
if db == nil {
|
|
t.Skip("Postgres not available, skipping test")
|
|
}
|
|
|
|
// Clean slate — drop test-owned tables but preserve the shared migrations
|
|
// table so concurrent test packages (e.g. configstore) are not disrupted.
|
|
db.Exec("DROP MATERIALIZED VIEW IF EXISTS mv_logs_hourly CASCADE")
|
|
db.Exec("DROP MATERIALIZED VIEW IF EXISTS mv_logs_filterdata CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS mcp_tool_logs CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS async_jobs CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS logs CASCADE")
|
|
db.Exec("CREATE TABLE IF NOT EXISTS migrations (id VARCHAR(255) PRIMARY KEY)")
|
|
db.Exec("DELETE FROM migrations")
|
|
|
|
ctx := context.Background()
|
|
err := triggerMigrations(ctx, db)
|
|
require.NoError(t, err, "migrations should succeed")
|
|
|
|
err = ensureMatViews(ctx, db)
|
|
require.NoError(t, err, "matview creation should succeed")
|
|
|
|
store := &RDBLogStore{db: db}
|
|
|
|
t.Cleanup(func() {
|
|
for _, idx := range performanceIndexes {
|
|
db.Exec("DROP INDEX IF EXISTS " + idx.name)
|
|
}
|
|
db.Exec("DROP MATERIALIZED VIEW IF EXISTS mv_logs_hourly CASCADE")
|
|
db.Exec("DROP MATERIALIZED VIEW IF EXISTS mv_logs_filterdata CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS mcp_tool_logs CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS async_jobs CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS logs CASCADE")
|
|
db.Exec("DELETE FROM migrations")
|
|
})
|
|
|
|
return store, db
|
|
}
|
|
|
|
// acquirePerfTestSQLConn returns a dedicated connection for ensurePerformanceIndexes (CONCURRENTLY + session SET).
|
|
func acquirePerfTestSQLConn(t *testing.T, ctx context.Context, db *gorm.DB) *sql.Conn {
|
|
t.Helper()
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
conn, err := sqlDB.Conn(ctx)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = conn.Close() })
|
|
return conn
|
|
}
|
|
|
|
type logOpts struct {
|
|
Model string
|
|
Provider string
|
|
Status string
|
|
Timestamp time.Time
|
|
RoutingEnginesUsed string
|
|
Metadata string
|
|
ContentSummary string
|
|
VirtualKeyID string
|
|
VirtualKeyName string
|
|
SelectedKeyID string
|
|
SelectedKeyName string
|
|
RoutingRuleID string
|
|
RoutingRuleName string
|
|
}
|
|
|
|
func insertPerfLog(t *testing.T, db *gorm.DB, opts logOpts) {
|
|
t.Helper()
|
|
if opts.Provider == "" {
|
|
opts.Provider = "openai"
|
|
}
|
|
if opts.Status == "" {
|
|
opts.Status = "success"
|
|
}
|
|
if opts.Model == "" {
|
|
opts.Model = "gpt-4"
|
|
}
|
|
id := uuid.New().String()
|
|
err := db.Exec(`
|
|
INSERT INTO logs (id, timestamp, object_type, provider, model, status,
|
|
routing_engines_used, metadata, content_summary,
|
|
virtual_key_id, virtual_key_name, selected_key_id, selected_key_name,
|
|
routing_rule_id, routing_rule_name, created_at, latency, cost,
|
|
prompt_tokens, completion_tokens, total_tokens)
|
|
VALUES (?, ?, 'chat_completion', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 100, 0.01, 10, 5, 15)
|
|
`, id, opts.Timestamp, opts.Provider, opts.Model, opts.Status,
|
|
opts.RoutingEnginesUsed, opts.Metadata, opts.ContentSummary,
|
|
opts.VirtualKeyID, opts.VirtualKeyName, opts.SelectedKeyID, opts.SelectedKeyName,
|
|
opts.RoutingRuleID, opts.RoutingRuleName, opts.Timestamp).Error
|
|
require.NoError(t, err, "Failed to insert test log")
|
|
}
|
|
|
|
type mcpLogOpts struct {
|
|
ToolName string
|
|
ServerLabel string
|
|
Timestamp time.Time
|
|
VirtualKeyID string
|
|
VirtualKeyName string
|
|
Arguments string
|
|
Result string
|
|
}
|
|
|
|
func insertPerfMCPLog(t *testing.T, db *gorm.DB, opts mcpLogOpts) {
|
|
t.Helper()
|
|
id := uuid.New().String()
|
|
err := db.Exec(`
|
|
INSERT INTO mcp_tool_logs (id, llm_request_id, tool_name, server_label,
|
|
timestamp, status, latency, cost,
|
|
virtual_key_id, virtual_key_name, arguments, result, created_at)
|
|
VALUES (?, ?, ?, ?, ?, 'success', 50, 0.001, ?, ?, ?, ?, ?)
|
|
`, id, uuid.New().String(), opts.ToolName, opts.ServerLabel,
|
|
opts.Timestamp, opts.VirtualKeyID, opts.VirtualKeyName,
|
|
opts.Arguments, opts.Result, opts.Timestamp).Error
|
|
require.NoError(t, err, "Failed to insert MCP test log")
|
|
}
|
|
|
|
// refreshTestMatViews refreshes materialized views after inserting test data.
|
|
// This is needed because matviews are populated at creation time and don't
|
|
// automatically reflect new inserts until explicitly refreshed.
|
|
func refreshTestMatViews(t *testing.T, db *gorm.DB) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
err := refreshMatViews(ctx, db)
|
|
require.NoError(t, err, "Failed to refresh materialized views")
|
|
}
|
|
|
|
// ---------- Phase 1: Defensive Limits ----------
|
|
|
|
func TestSearchLogs_LimitClamping(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
|
|
for i := 0; i < 5; i++ {
|
|
insertPerfLog(t, db, logOpts{Timestamp: now})
|
|
}
|
|
refreshTestMatViews(t, db)
|
|
|
|
// Limit=0 should be clamped (not return 0 results)
|
|
result, err := store.SearchLogs(ctx, SearchFilters{}, PaginationOptions{Limit: 0})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 5, len(result.Logs), "Limit=0 should be clamped")
|
|
|
|
// Limit=2 should return 2
|
|
result, err = store.SearchLogs(ctx, SearchFilters{}, PaginationOptions{Limit: 2})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, len(result.Logs))
|
|
|
|
// Limit=-1 should be clamped
|
|
result, err = store.SearchLogs(ctx, SearchFilters{}, PaginationOptions{Limit: -1})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 5, len(result.Logs), "Limit=-1 should be clamped")
|
|
|
|
// Limit=2000 should be clamped to 1000
|
|
result, err = store.SearchLogs(ctx, SearchFilters{}, PaginationOptions{Limit: 2000})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 5, len(result.Logs))
|
|
}
|
|
|
|
func TestSearchMCPToolLogs_LimitClamping(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
|
|
for i := 0; i < 5; i++ {
|
|
insertPerfMCPLog(t, db, mcpLogOpts{
|
|
ToolName: "search", ServerLabel: "s1", Timestamp: now,
|
|
VirtualKeyID: "vk-1", VirtualKeyName: "key-1",
|
|
})
|
|
}
|
|
|
|
result, err := store.SearchMCPToolLogs(ctx, MCPToolLogSearchFilters{}, PaginationOptions{Limit: 0})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 5, len(result.Logs), "Limit=0 should be clamped")
|
|
|
|
result, err = store.SearchMCPToolLogs(ctx, MCPToolLogSearchFilters{}, PaginationOptions{Limit: 3})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, len(result.Logs))
|
|
}
|
|
|
|
func TestGetModelRankings_HasLimit(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
start := now.Add(-1 * time.Hour)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
insertPerfLog(t, db, logOpts{
|
|
Model: fmt.Sprintf("model-%d", i), Timestamp: now,
|
|
})
|
|
}
|
|
refreshTestMatViews(t, db)
|
|
|
|
result, err := store.GetModelRankings(ctx, SearchFilters{StartTime: &start, EndTime: &now})
|
|
require.NoError(t, err)
|
|
assert.LessOrEqual(t, len(result.Rankings), defaultMaxRankingsLimit)
|
|
assert.Equal(t, 5, len(result.Rankings))
|
|
}
|
|
|
|
func TestDeleteExpiredAsyncJobs_BatchDeletes(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
past := time.Now().UTC().Add(-1 * time.Hour)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
err := db.Exec(`
|
|
INSERT INTO async_jobs (id, status, request_type, virtual_key_id, expires_at, created_at)
|
|
VALUES (?, 'completed', 'chat_completion', 'vk-1', ?, ?)
|
|
`, uuid.New().String(), past, past).Error
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
deleted, err := store.DeleteExpiredAsyncJobs(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(5), deleted)
|
|
|
|
var count int64
|
|
db.Model(&AsyncJob{}).Count(&count)
|
|
assert.Equal(t, int64(0), count)
|
|
}
|
|
|
|
// ---------- Phase 2: Time-scoped filter data ----------
|
|
|
|
func TestGetDistinctModels_TimeCutoff(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
recent := now.Add(-7 * 24 * time.Hour)
|
|
old := now.Add(-60 * 24 * time.Hour)
|
|
|
|
insertPerfLog(t, db, logOpts{Model: "recent-model", Timestamp: recent})
|
|
insertPerfLog(t, db, logOpts{Model: "old-model", Timestamp: old})
|
|
refreshTestMatViews(t, db)
|
|
|
|
models, err := store.GetDistinctModels(ctx)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, models, "recent-model")
|
|
assert.NotContains(t, models, "old-model")
|
|
}
|
|
|
|
func TestGetDistinctKeyPairs_TimeCutoff(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
recent := now.Add(-7 * 24 * time.Hour)
|
|
old := now.Add(-60 * 24 * time.Hour)
|
|
|
|
insertPerfLog(t, db, logOpts{
|
|
Timestamp: recent, VirtualKeyID: "vk-recent", VirtualKeyName: "Recent Key",
|
|
})
|
|
insertPerfLog(t, db, logOpts{
|
|
Timestamp: old, VirtualKeyID: "vk-old", VirtualKeyName: "Old Key",
|
|
})
|
|
refreshTestMatViews(t, db)
|
|
|
|
pairs, err := store.GetDistinctKeyPairs(ctx, "virtual_key_id", "virtual_key_name")
|
|
require.NoError(t, err)
|
|
|
|
var ids []string
|
|
for _, p := range pairs {
|
|
ids = append(ids, p.ID)
|
|
}
|
|
assert.Contains(t, ids, "vk-recent")
|
|
assert.NotContains(t, ids, "vk-old")
|
|
}
|
|
|
|
func TestGetDistinctRoutingEngines_TimeCutoff(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
recent := now.Add(-7 * 24 * time.Hour)
|
|
old := now.Add(-60 * 24 * time.Hour)
|
|
|
|
insertPerfLog(t, db, logOpts{
|
|
Timestamp: recent, RoutingEnginesUsed: "loadbalancing,governance",
|
|
})
|
|
insertPerfLog(t, db, logOpts{
|
|
Timestamp: old, RoutingEnginesUsed: "routing-rule",
|
|
})
|
|
refreshTestMatViews(t, db)
|
|
|
|
engines, err := store.GetDistinctRoutingEngines(ctx)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, engines, "loadbalancing")
|
|
assert.Contains(t, engines, "governance")
|
|
assert.NotContains(t, engines, "routing-rule")
|
|
}
|
|
|
|
func TestGetDistinctMetadataKeys_TimeCutoff(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
recent := now.Add(-7 * 24 * time.Hour)
|
|
old := now.Add(-60 * 24 * time.Hour)
|
|
|
|
insertPerfLog(t, db, logOpts{
|
|
Timestamp: recent, Metadata: `{"env": "production"}`,
|
|
})
|
|
insertPerfLog(t, db, logOpts{
|
|
Timestamp: old, Metadata: `{"old_key": "old_value"}`,
|
|
})
|
|
|
|
keys, err := store.GetDistinctMetadataKeys(ctx)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, keys, "env")
|
|
assert.NotContains(t, keys, "old_key")
|
|
}
|
|
|
|
func TestGetAvailableToolNames_TimeCutoff(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
recent := now.Add(-7 * 24 * time.Hour)
|
|
old := now.Add(-60 * 24 * time.Hour)
|
|
|
|
insertPerfMCPLog(t, db, mcpLogOpts{
|
|
ToolName: "recent-tool", ServerLabel: "s1", Timestamp: recent,
|
|
VirtualKeyID: "vk-1", VirtualKeyName: "k1",
|
|
})
|
|
insertPerfMCPLog(t, db, mcpLogOpts{
|
|
ToolName: "old-tool", ServerLabel: "s1", Timestamp: old,
|
|
VirtualKeyID: "vk-1", VirtualKeyName: "k1",
|
|
})
|
|
|
|
tools, err := store.GetAvailableToolNames(ctx)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, tools, "recent-tool")
|
|
assert.NotContains(t, tools, "old-tool")
|
|
}
|
|
|
|
func TestGetAvailableServerLabels_TimeCutoff(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
recent := now.Add(-7 * 24 * time.Hour)
|
|
old := now.Add(-60 * 24 * time.Hour)
|
|
|
|
insertPerfMCPLog(t, db, mcpLogOpts{
|
|
ToolName: "t1", ServerLabel: "recent-server", Timestamp: recent,
|
|
VirtualKeyID: "vk-1", VirtualKeyName: "k1",
|
|
})
|
|
insertPerfMCPLog(t, db, mcpLogOpts{
|
|
ToolName: "t2", ServerLabel: "old-server", Timestamp: old,
|
|
VirtualKeyID: "vk-1", VirtualKeyName: "k1",
|
|
})
|
|
|
|
labels, err := store.GetAvailableServerLabels(ctx)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, labels, "recent-server")
|
|
assert.NotContains(t, labels, "old-server")
|
|
}
|
|
|
|
func TestGetAvailableMCPVirtualKeys_TimeCutoff(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
recent := now.Add(-7 * 24 * time.Hour)
|
|
old := now.Add(-60 * 24 * time.Hour)
|
|
|
|
insertPerfMCPLog(t, db, mcpLogOpts{
|
|
ToolName: "t1", ServerLabel: "s1", Timestamp: recent,
|
|
VirtualKeyID: "vk-recent", VirtualKeyName: "Recent VK",
|
|
})
|
|
insertPerfMCPLog(t, db, mcpLogOpts{
|
|
ToolName: "t2", ServerLabel: "s1", Timestamp: old,
|
|
VirtualKeyID: "vk-old", VirtualKeyName: "Old VK",
|
|
})
|
|
|
|
keys, err := store.GetAvailableMCPVirtualKeys(ctx)
|
|
require.NoError(t, err)
|
|
|
|
var ids []string
|
|
for _, k := range keys {
|
|
if k.VirtualKeyID != nil {
|
|
ids = append(ids, *k.VirtualKeyID)
|
|
}
|
|
}
|
|
assert.Contains(t, ids, "vk-recent")
|
|
assert.NotContains(t, ids, "vk-old")
|
|
}
|
|
|
|
// ---------- Phase 3: Routing engine filter + indexes ----------
|
|
|
|
func TestRoutingEngineFilter_Postgres(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
start := now.Add(-1 * time.Hour)
|
|
|
|
insertPerfLog(t, db, logOpts{
|
|
Model: "m1", Timestamp: now, RoutingEnginesUsed: "loadbalancing,governance",
|
|
})
|
|
insertPerfLog(t, db, logOpts{
|
|
Model: "m2", Timestamp: now, RoutingEnginesUsed: "routing-rule",
|
|
})
|
|
insertPerfLog(t, db, logOpts{
|
|
Model: "m3", Timestamp: now, RoutingEnginesUsed: "loadbalancing",
|
|
})
|
|
|
|
// Single engine filter
|
|
result, err := store.SearchLogs(ctx, SearchFilters{
|
|
RoutingEngineUsed: []string{"loadbalancing"},
|
|
StartTime: &start, EndTime: &now,
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, len(result.Logs), "Should find 2 logs with loadbalancing")
|
|
|
|
result, err = store.SearchLogs(ctx, SearchFilters{
|
|
RoutingEngineUsed: []string{"governance"},
|
|
StartTime: &start, EndTime: &now,
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(result.Logs), "Should find 1 log with governance")
|
|
|
|
result, err = store.SearchLogs(ctx, SearchFilters{
|
|
RoutingEngineUsed: []string{"routing-rule"},
|
|
StartTime: &start, EndTime: &now,
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(result.Logs), "Should find 1 log with routing-rule")
|
|
|
|
// Multiple engines (OR)
|
|
result, err = store.SearchLogs(ctx, SearchFilters{
|
|
RoutingEngineUsed: []string{"loadbalancing", "routing-rule"},
|
|
StartTime: &start, EndTime: &now,
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, len(result.Logs), "Should find all 3 with loadbalancing OR routing-rule")
|
|
|
|
// Non-existent engine
|
|
result, err = store.SearchLogs(ctx, SearchFilters{
|
|
RoutingEngineUsed: []string{"nonexistent"},
|
|
StartTime: &start, EndTime: &now,
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(result.Logs))
|
|
}
|
|
|
|
func TestEnsurePerformanceIndexes(t *testing.T) {
|
|
db := trySetupPostgresDB(t)
|
|
if db == nil {
|
|
t.Skip("Postgres not available, skipping test")
|
|
}
|
|
|
|
db.Exec("DROP TABLE IF EXISTS mcp_tool_logs CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS async_jobs CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS logs CASCADE")
|
|
db.Exec("CREATE TABLE IF NOT EXISTS migrations (id VARCHAR(255) PRIMARY KEY)")
|
|
db.Exec("DELETE FROM migrations")
|
|
|
|
ctx := context.Background()
|
|
err := triggerMigrations(ctx, db)
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
for _, idx := range performanceIndexes {
|
|
db.Exec("DROP INDEX IF EXISTS " + idx.name)
|
|
}
|
|
db.Exec("DROP TABLE IF EXISTS mcp_tool_logs CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS async_jobs CASCADE")
|
|
db.Exec("DROP TABLE IF EXISTS logs CASCADE")
|
|
db.Exec("DELETE FROM migrations")
|
|
})
|
|
|
|
conn := acquirePerfTestSQLConn(t, ctx, db)
|
|
// First run
|
|
err = ensurePerformanceIndexes(ctx, conn)
|
|
require.NoError(t, err, "ensurePerformanceIndexes should succeed")
|
|
|
|
// Verify all indexes exist and are valid
|
|
for _, idx := range performanceIndexes {
|
|
var indexValid bool
|
|
err := db.Raw(`
|
|
SELECT COALESCE(bool_and(pi.indisvalid), false)
|
|
FROM pg_class pc
|
|
JOIN pg_index pi ON pi.indrelid = pc.oid
|
|
JOIN pg_class ic ON ic.oid = pi.indexrelid
|
|
WHERE pc.relname = ?
|
|
AND ic.relname = ?
|
|
`, idx.table, idx.name).Scan(&indexValid).Error
|
|
require.NoError(t, err)
|
|
assert.True(t, indexValid, "Index %s should be valid", idx.name)
|
|
}
|
|
|
|
// Idempotent — second run should be a no-op
|
|
err = ensurePerformanceIndexes(ctx, conn)
|
|
require.NoError(t, err, "ensurePerformanceIndexes should be idempotent")
|
|
}
|
|
|
|
func TestContentSearch_Postgres(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
start := now.Add(-1 * time.Hour)
|
|
|
|
// Build indexes
|
|
conn := acquirePerfTestSQLConn(t, ctx, db)
|
|
|
|
err := ensurePerformanceIndexes(ctx, conn)
|
|
require.NoError(t, err)
|
|
|
|
insertPerfLog(t, db, logOpts{
|
|
Timestamp: now,
|
|
ContentSummary: "The quick brown fox jumps over the lazy dog",
|
|
})
|
|
insertPerfLog(t, db, logOpts{
|
|
Timestamp: now,
|
|
ContentSummary: "Hello world this is a test message",
|
|
})
|
|
|
|
result, err := store.SearchLogs(ctx, SearchFilters{
|
|
ContentSearch: "brown fox",
|
|
StartTime: &start, EndTime: &now,
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(result.Logs), "Should find 1 log matching 'brown fox'")
|
|
|
|
result, err = store.SearchLogs(ctx, SearchFilters{
|
|
ContentSearch: "test message",
|
|
StartTime: &start, EndTime: &now,
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(result.Logs), "Should find 1 log matching 'test message'")
|
|
|
|
result, err = store.SearchLogs(ctx, SearchFilters{
|
|
ContentSearch: "nonexistent phrase",
|
|
StartTime: &start, EndTime: &now,
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, len(result.Logs))
|
|
}
|
|
|
|
func TestMCPContentSearch_Postgres(t *testing.T) {
|
|
store, db := setupPerfTestDB(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
|
|
// Build indexes
|
|
conn := acquirePerfTestSQLConn(t, ctx, db)
|
|
err := ensurePerformanceIndexes(ctx, conn)
|
|
require.NoError(t, err)
|
|
|
|
insertPerfMCPLog(t, db, mcpLogOpts{
|
|
ToolName: "search", ServerLabel: "s1", Timestamp: now,
|
|
VirtualKeyID: "vk-1", VirtualKeyName: "k1",
|
|
Arguments: `{"query": "weather in london"}`,
|
|
Result: `{"temperature": 15}`,
|
|
})
|
|
insertPerfMCPLog(t, db, mcpLogOpts{
|
|
ToolName: "calc", ServerLabel: "s1", Timestamp: now,
|
|
VirtualKeyID: "vk-1", VirtualKeyName: "k1",
|
|
Arguments: `{"expression": "2+2"}`,
|
|
Result: `{"answer": 4}`,
|
|
})
|
|
|
|
result, err := store.SearchMCPToolLogs(ctx, MCPToolLogSearchFilters{
|
|
ContentSearch: "london",
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(result.Logs), "Should find 1 MCP log matching 'london'")
|
|
|
|
result, err = store.SearchMCPToolLogs(ctx, MCPToolLogSearchFilters{
|
|
ContentSearch: "temperature",
|
|
}, PaginationOptions{Limit: 100})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, len(result.Logs), "Should find 1 MCP log matching 'temperature' in result")
|
|
}
|