108 lines
3.8 KiB
Go
108 lines
3.8 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"yingce/backend/internal/model"
|
|
"yingce/backend/internal/repository"
|
|
)
|
|
|
|
func TestCloudAgentReadTextRuntimeReadsResourceOutsideCheckpoint(t *testing.T) {
|
|
s, db, _, _ := creationTestService(t)
|
|
// A service read inside the checkpoint transaction would exhaust this pool.
|
|
sqlDB, err := db.DB()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sqlDB.SetMaxOpenConns(1)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
s.repo = s.repo.WithContext(ctx)
|
|
|
|
content := "第一章\n林逸在石殿中醒来。"
|
|
objectKey := "text-runtime/novel.txt"
|
|
if err := os.MkdirAll(filepath.Join(s.dataDir, "resources", filepath.Dir(objectKey)), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(s.dataDir, "resources", objectKey), []byte(content), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, resource := range []model.Resource{
|
|
{ID: "owned-text", UserID: "user", Kind: "file", Status: model.ResourceStatusReady, Provider: "local", ObjectKey: objectKey, MimeType: "text/plain", Size: int64(len(content))},
|
|
{ID: "foreign-text", UserID: "other-user", Kind: "file", Status: model.ResourceStatusReady, Provider: "local", ObjectKey: objectKey, MimeType: "text/plain", Size: int64(len(content))},
|
|
} {
|
|
if err := db.Create(&resource).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
canvas := model.CanvasProject{ID: "agent-canvas", UserID: "user", PayloadJSON: `{"nodes":[{"id":"note","type":"text","title":"Note","metadata":{"content":"/api/resources/owned-text/file"}},{"id":"foreign","type":"text","metadata":{"storageKey":"resource:foreign-text"}}],"connections":[]}`}
|
|
if err := db.Create(&canvas).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root, err := s.CreateCloudAgentRun("user", agentTestRequest(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cases := []struct {
|
|
args, want string
|
|
next int
|
|
more, fail bool
|
|
}{
|
|
{args: `{"nodeId":"note","maxChars":4}`, want: "第一章\n", next: 4, more: true},
|
|
{args: `{"nodeId":"note","offset":4}`, want: "林逸在石殿中醒来。", next: len([]rune(content))},
|
|
{args: `{"nodeId":"foreign"}`, fail: true},
|
|
}
|
|
calls := make([]cloudAgentCall, len(cases))
|
|
for i, tc := range cases {
|
|
calls[i].ID = fmt.Sprintf("read-text-%d", i)
|
|
calls[i].Function.Name = "canvas_read_text"
|
|
calls[i].Function.Arguments = tc.args
|
|
}
|
|
run, err := s.repo.CloudAgent("user", root.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state, err := cloudAgentDecode(run)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state.Calls, state.CallIndex = calls, 0
|
|
state.Canonical.Messages = append(state.Canonical.Messages, map[string]any{"role": "assistant", "content": "", "tool_calls": calls})
|
|
if err := s.repo.MutateCloudAgent("user", run.ID, run.Revision, func(current *model.CloudAgentExecution, _ *repository.Repository) error {
|
|
return cloudAgentSave(current, &state)
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i, tc := range cases {
|
|
result, err := s.executeCloudAgentRuntimeTool(ctx, "user", root.ID, calls[i])
|
|
if err != nil {
|
|
t.Fatalf("call %d: %v", i, err)
|
|
}
|
|
payload, ok := result.(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("unexpected bridge result: %#v", result)
|
|
}
|
|
var view map[string]any
|
|
if err := json.Unmarshal([]byte(stringValue(payload["content"])), &view); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tc.fail {
|
|
if view["error"] == nil || view["content"] != nil {
|
|
t.Fatalf("foreign resource was exposed: %#v", view)
|
|
}
|
|
} else if view["error"] != nil || view["content"] != tc.want || view["nextOffset"] != float64(tc.next) || view["hasMore"] != tc.more {
|
|
t.Fatalf("call %d returned wrong text page: %#v", i, view)
|
|
}
|
|
}
|
|
stored, err := s.repo.CanvasProjectForUser("user", canvas.ID)
|
|
if err != nil || stored.PayloadJSON != canvas.PayloadJSON {
|
|
t.Fatalf("text read changed canvas: %v", err)
|
|
}
|
|
}
|