yao/agent/context/jsapi_memory_test.go
Max 632c0f5674 Refactor Context Management to Use Memory Instead of Space
- Replaced all instances of `ctx.Space` with `ctx.Memory.Context` in the context management code, ensuring a more structured approach to handling temporary request-scoped data.
- Updated related test cases to reflect the changes in context memory usage, enhancing the reliability and clarity of tests.
- Removed the deprecated `Space` references and adjusted comments and documentation to align with the new memory management strategy.
2025-12-22 11:19:00 +08:00

561 lines
14 KiB
Go

package context_test
import (
stdContext "context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v8 "github.com/yaoapp/gou/runtime/v8"
"github.com/yaoapp/yao/agent/context"
"github.com/yaoapp/yao/agent/memory"
"github.com/yaoapp/yao/config"
"github.com/yaoapp/yao/openapi/oauth/types"
"github.com/yaoapp/yao/test"
)
func TestMemoryUserNamespace(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1")
require.NoError(t, err)
ctx := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Locale: "en",
Context: stdContext.Background(),
Memory: mem,
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Set values in user namespace
ctx.memory.user.Set("name", "John");
ctx.memory.user.Set("age", 30);
ctx.memory.user.Set("active", true);
// Get values back
const name = ctx.memory.user.Get("name");
const age = ctx.memory.user.Get("age");
const active = ctx.memory.user.Get("active");
// Verify
if (name !== "John") throw new Error("Name mismatch");
if (age !== 30) throw new Error("Age mismatch");
if (active !== true) throw new Error("Active mismatch");
return {
success: true,
name: name,
age: age,
active: active
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
assert.Equal(t, "John", result["name"])
assert.Equal(t, float64(30), result["age"])
assert.Equal(t, true, result["active"])
}
func TestMemoryTeamNamespace(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1")
require.NoError(t, err)
ctx := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Locale: "en",
Context: stdContext.Background(),
Memory: mem,
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Set team-wide settings
ctx.memory.team.Set("settings", { theme: "dark", language: "en" });
// Get back
const settings = ctx.memory.team.Get("settings");
if (settings.theme !== "dark") throw new Error("Theme mismatch");
if (settings.language !== "en") throw new Error("Language mismatch");
return {
success: true,
settings: settings
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
}
func TestMemoryChatNamespace(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1")
require.NoError(t, err)
ctx := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Locale: "en",
Context: stdContext.Background(),
Memory: mem,
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Set chat context
ctx.memory.chat.Set("topic", "AI Discussion");
ctx.memory.chat.Set("participants", ["Alice", "Bob"]);
// Get back
const topic = ctx.memory.chat.Get("topic");
const participants = ctx.memory.chat.Get("participants");
if (topic !== "AI Discussion") throw new Error("Topic mismatch");
if (participants.length !== 2) throw new Error("Participants mismatch");
return {
success: true,
topic: topic,
participants: participants
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
assert.Equal(t, "AI Discussion", result["topic"])
}
func TestMemoryContextNamespace(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1")
require.NoError(t, err)
ctx := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Locale: "en",
Context: stdContext.Background(),
Memory: mem,
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Set temporary context data
ctx.memory.context.Set("temp_result", { step: 1, data: "processing" });
// Get back
const result = ctx.memory.context.Get("temp_result");
if (result.step !== 1) throw new Error("Step mismatch");
if (result.data !== "processing") throw new Error("Data mismatch");
return {
success: true,
result: result
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
}
func TestMemoryHasAndDel(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1")
require.NoError(t, err)
ctx := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Locale: "en",
Context: stdContext.Background(),
Memory: mem,
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Set a value
ctx.memory.user.Set("key", "value");
// Check Has
const hasBefore = ctx.memory.user.Has("key");
if (!hasBefore) throw new Error("Should have key before delete");
// Delete
ctx.memory.user.Del("key");
// Check Has again
const hasAfter = ctx.memory.user.Has("key");
if (hasAfter) throw new Error("Should not have key after delete");
return {
success: true,
hasBefore: hasBefore,
hasAfter: hasAfter
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
assert.True(t, result["hasBefore"].(bool))
assert.False(t, result["hasAfter"].(bool))
}
func TestMemoryIncrDecr(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1")
require.NoError(t, err)
ctx := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Locale: "en",
Context: stdContext.Background(),
Memory: mem,
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Incr on non-existent key
const v1 = ctx.memory.user.Incr("counter");
if (v1 !== 1) throw new Error("First incr should be 1, got " + v1);
// Incr with delta
const v2 = ctx.memory.user.Incr("counter", 5);
if (v2 !== 6) throw new Error("Second incr should be 6, got " + v2);
// Decr
const v3 = ctx.memory.user.Decr("counter", 2);
if (v3 !== 4) throw new Error("Decr should be 4, got " + v3);
return {
success: true,
v1: v1,
v2: v2,
v3: v3
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
assert.Equal(t, float64(1), result["v1"])
assert.Equal(t, float64(6), result["v2"])
assert.Equal(t, float64(4), result["v3"])
}
func TestMemoryKeysAndLen(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Use unique IDs to avoid data pollution from other tests
mem, err := memory.New(nil, "user-keys-len", "team-keys-len", "chat-keys-len", "ctx-keys-len")
require.NoError(t, err)
ctx := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Locale: "en",
Context: stdContext.Background(),
Memory: mem,
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Set multiple values
ctx.memory.user.Set("a", 1);
ctx.memory.user.Set("b", 2);
ctx.memory.user.Set("c", 3);
// Get keys
const keys = ctx.memory.user.Keys();
if (keys.length !== 3) throw new Error("Should have 3 keys, got " + keys.length);
// Get len
const len = ctx.memory.user.Len();
if (len !== 3) throw new Error("Len should be 3, got " + len);
return {
success: true,
keys: keys,
len: len
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
if !result["success"].(bool) {
t.Fatalf("Test failed: %v", result["error"])
}
assert.Equal(t, float64(3), result["len"])
}
func TestMemoryClear(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1")
require.NoError(t, err)
ctx := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Locale: "en",
Context: stdContext.Background(),
Memory: mem,
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Set values
ctx.memory.user.Set("a", 1);
ctx.memory.user.Set("b", 2);
// Clear
ctx.memory.user.Clear();
// Check len
const len = ctx.memory.user.Len();
if (len !== 0) throw new Error("Len should be 0 after clear, got " + len);
return {
success: true,
len: len
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
assert.Equal(t, float64(0), result["len"])
}
func TestMemoryGetDel(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
mem, err := memory.New(nil, "user1", "team1", "chat1", "ctx1")
require.NoError(t, err)
ctx := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Locale: "en",
Context: stdContext.Background(),
Memory: mem,
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Set a one-time value
ctx.memory.user.Set("token", "secret123");
// GetDel
const value = ctx.memory.user.GetDel("token");
if (value !== "secret123") throw new Error("Value mismatch");
// Should be deleted
const after = ctx.memory.user.Get("token");
if (after !== null) throw new Error("Should be null after GetDel");
return {
success: true,
value: value,
after: after
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
assert.Equal(t, "secret123", result["value"])
assert.Nil(t, result["after"])
}
func TestMemoryIsolation(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Create two different memory instances
mem1, err := memory.New(nil, "user1", "", "", "")
require.NoError(t, err)
mem2, err := memory.New(nil, "user2", "", "", "")
require.NoError(t, err)
ctx1 := &context.Context{
ChatID: "chat1",
Context: stdContext.Background(),
Memory: mem1,
}
ctx2 := &context.Context{
ChatID: "chat2",
Context: stdContext.Background(),
Memory: mem2,
}
// Set value in user1
res1, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
ctx.memory.user.Set("key", "user1_value");
return ctx.memory.user.Get("key");
}`, ctx1)
require.NoError(t, err)
assert.Equal(t, "user1_value", res1)
// Set value in user2
res2, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
ctx.memory.user.Set("key", "user2_value");
return ctx.memory.user.Get("key");
}`, ctx2)
require.NoError(t, err)
assert.Equal(t, "user2_value", res2)
// Verify user1 still has its own value
res3, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
return ctx.memory.user.Get("key");
}`, ctx1)
require.NoError(t, err)
assert.Equal(t, "user1_value", res3)
}
func TestMemoryNoMemory(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
ctx := &context.Context{
ChatID: "test-chat-id",
Context: stdContext.Background(),
Memory: nil, // No memory
}
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
const hasMemory = ctx.memory !== undefined && ctx.memory !== null;
return {
success: true,
hasMemory: hasMemory
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
assert.False(t, result["hasMemory"].(bool))
}
func TestMemoryWithAuthorizedInfo(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
// Use context.New to create context with authorized info
authorized := &types.AuthorizedInfo{
UserID: "user123",
TeamID: "team456",
}
ctx := context.New(stdContext.Background(), authorized, "chat789")
defer ctx.Release()
// Verify memory was created with correct IDs
require.NotNil(t, ctx.Memory)
require.NotNil(t, ctx.Memory.User)
require.NotNil(t, ctx.Memory.Team)
require.NotNil(t, ctx.Memory.Chat)
require.NotNil(t, ctx.Memory.Context)
res, err := v8.Call(v8.CallOptions{}, `
function test(ctx) {
try {
// Set values in different namespaces
ctx.memory.user.Set("pref", "dark");
ctx.memory.team.Set("setting", "shared");
ctx.memory.chat.Set("topic", "test");
ctx.memory.context.Set("temp", "data");
return {
success: true,
user: ctx.memory.user.Get("pref"),
team: ctx.memory.team.Get("setting"),
chat: ctx.memory.chat.Get("topic"),
context: ctx.memory.context.Get("temp")
};
} catch (error) {
return { success: false, error: error.message };
}
}`, ctx)
require.NoError(t, err)
result := res.(map[string]interface{})
assert.True(t, result["success"].(bool))
assert.Equal(t, "dark", result["user"])
assert.Equal(t, "shared", result["team"])
assert.Equal(t, "test", result["chat"])
assert.Equal(t, "data", result["context"])
}