mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-15 03:57:30 +08:00
Refactor runtime around world core
This commit is contained in:
527
pkg/agent/world_runtime_test.go
Normal file
527
pkg/agent/world_runtime_test.go
Normal file
@@ -0,0 +1,527 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/world"
|
||||
)
|
||||
|
||||
func TestWorldRuntimeHandleUserInputInitializesState(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
manager := tools.NewAgentManager(nil, workspace, nil)
|
||||
store := manager.ProfileStore()
|
||||
if store == nil {
|
||||
t.Fatalf("expected profile store")
|
||||
}
|
||||
if _, err := store.Upsert(tools.AgentProfile{
|
||||
AgentID: "keeper",
|
||||
Name: "Keeper",
|
||||
Kind: "npc",
|
||||
Persona: "A calm keeper of the commons.",
|
||||
HomeLocation: "commons",
|
||||
DefaultGoals: []string{"watch the square"},
|
||||
Status: "active",
|
||||
}); err != nil {
|
||||
t.Fatalf("profile upsert failed: %v", err)
|
||||
}
|
||||
manager.SetRunFunc(func(ctx context.Context, task *tools.AgentTask) (string, error) {
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"actor_id": task.AgentID,
|
||||
"action": "speak",
|
||||
"speech": "I saw the user arrive.",
|
||||
})
|
||||
return string(out), nil
|
||||
})
|
||||
runtime := NewWorldRuntime(workspace, store, tools.NewAgentDispatcher(manager), manager)
|
||||
|
||||
out, err := runtime.HandleUserInput(context.Background(), "I enter the commons.", "cli", "direct")
|
||||
if err != nil {
|
||||
t.Fatalf("handle user input failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "keeper") && !strings.Contains(out, "I saw the user arrive") {
|
||||
t.Fatalf("unexpected world response: %q", out)
|
||||
}
|
||||
|
||||
for _, name := range []string{"world_state.json", "npc_state.json", "world_events.jsonl"} {
|
||||
if _, err := os.Stat(filepath.Join(workspace, "agents", "runtime", name)); err != nil {
|
||||
t.Fatalf("expected world artifact %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
snapshot, err := runtime.Snapshot(10)
|
||||
if err != nil {
|
||||
t.Fatalf("snapshot failed: %v", err)
|
||||
}
|
||||
data, _ := json.Marshal(snapshot)
|
||||
if !strings.Contains(string(data), "\"npc_count\":1") {
|
||||
t.Fatalf("expected snapshot npc_count=1, got %s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorldRuntimeTickSupportsAutonomousNPCAction(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
manager := tools.NewAgentManager(nil, workspace, nil)
|
||||
store := manager.ProfileStore()
|
||||
if store == nil {
|
||||
t.Fatalf("expected profile store")
|
||||
}
|
||||
if _, err := store.Upsert(tools.AgentProfile{
|
||||
AgentID: "patroller",
|
||||
Name: "Patroller",
|
||||
Kind: "npc",
|
||||
Persona: "Walks the route.",
|
||||
HomeLocation: "commons",
|
||||
DefaultGoals: []string{"patrol the area"},
|
||||
Status: "active",
|
||||
}); err != nil {
|
||||
t.Fatalf("profile upsert failed: %v", err)
|
||||
}
|
||||
manager.SetRunFunc(func(ctx context.Context, task *tools.AgentTask) (string, error) {
|
||||
out, _ := json.Marshal(map[string]interface{}{
|
||||
"actor_id": task.AgentID,
|
||||
"action": "move",
|
||||
"target_location": "square",
|
||||
})
|
||||
return string(out), nil
|
||||
})
|
||||
runtime := NewWorldRuntime(workspace, store, tools.NewAgentDispatcher(manager), manager)
|
||||
|
||||
out, err := runtime.Tick(context.Background(), "test")
|
||||
if err != nil {
|
||||
t.Fatalf("tick failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "square") {
|
||||
t.Fatalf("expected move narration, got %q", out)
|
||||
}
|
||||
npc, found, err := runtime.NPCGet("patroller")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("expected npc state after tick, found=%v err=%v", found, err)
|
||||
}
|
||||
data, _ := json.Marshal(npc)
|
||||
if !strings.Contains(string(data), "\"current_location\":\"square\"") {
|
||||
t.Fatalf("expected current_location square, got %s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorldRuntimeCreateNPCAndSnapshot(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
manager := tools.NewAgentManager(nil, workspace, nil)
|
||||
runtime := NewWorldRuntime(workspace, manager.ProfileStore(), tools.NewAgentDispatcher(manager), manager)
|
||||
|
||||
created, err := runtime.CreateNPC(context.Background(), map[string]interface{}{
|
||||
"npc_id": "merchant",
|
||||
"name": "Merchant",
|
||||
"persona": "Talkative trader",
|
||||
"home_location": "square",
|
||||
"default_goals": []string{"watch trade"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create npc failed: %v", err)
|
||||
}
|
||||
if got := strings.TrimSpace(tools.MapStringArg(created, "npc_id")); got != "merchant" {
|
||||
t.Fatalf("unexpected created npc id: %q", got)
|
||||
}
|
||||
|
||||
snapshotOut, err := runtime.Snapshot(10)
|
||||
if err != nil {
|
||||
t.Fatalf("snapshot failed: %v", err)
|
||||
}
|
||||
data, _ := json.Marshal(snapshotOut)
|
||||
if !strings.Contains(string(data), "\"merchant\"") {
|
||||
t.Fatalf("expected snapshot to include merchant: %s", string(data))
|
||||
}
|
||||
events, err := runtime.EventLog(10)
|
||||
if err != nil {
|
||||
t.Fatalf("event log failed: %v", err)
|
||||
}
|
||||
if len(events) == 0 {
|
||||
t.Fatalf("expected npc_created event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorldRuntimeCreateEntityAndGet(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
manager := tools.NewAgentManager(nil, workspace, nil)
|
||||
runtime := NewWorldRuntime(workspace, manager.ProfileStore(), tools.NewAgentDispatcher(manager), manager)
|
||||
created, err := runtime.CreateEntity(context.Background(), map[string]interface{}{
|
||||
"entity_id": "statue",
|
||||
"name": "Old Statue",
|
||||
"entity_type": "landmark",
|
||||
"location_id": "square",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create entity failed: %v", err)
|
||||
}
|
||||
if got := strings.TrimSpace(tools.MapStringArg(created, "entity_id")); got != "statue" {
|
||||
t.Fatalf("unexpected entity id: %q", got)
|
||||
}
|
||||
entity, found, err := runtime.EntityGet("statue")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("expected entity, found=%v err=%v", found, err)
|
||||
}
|
||||
if got := strings.TrimSpace(fmt.Sprint(entity["location_id"])); got != "square" {
|
||||
t.Fatalf("expected entity in square, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorldRuntimeSnapshotIncludesEntityOccupancyAfterInteract(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
manager := tools.NewAgentManager(nil, workspace, nil)
|
||||
store := manager.ProfileStore()
|
||||
if store == nil {
|
||||
t.Fatalf("expected profile store")
|
||||
}
|
||||
if _, err := store.Upsert(tools.AgentProfile{
|
||||
AgentID: "caretaker",
|
||||
Name: "Caretaker",
|
||||
Kind: "npc",
|
||||
Persona: "Maintains landmarks.",
|
||||
HomeLocation: "square",
|
||||
DefaultGoals: []string{"maintain landmarks"},
|
||||
Status: "active",
|
||||
}); err != nil {
|
||||
t.Fatalf("profile upsert failed: %v", err)
|
||||
}
|
||||
runtime := NewWorldRuntime(workspace, store, tools.NewAgentDispatcher(manager), manager)
|
||||
worldOut, err := runtime.WorldGet()
|
||||
if err != nil {
|
||||
t.Fatalf("world get failed: %v", err)
|
||||
}
|
||||
worldState, ok := worldOut["world_state"].(world.WorldState)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected world_state payload: %T", worldOut["world_state"])
|
||||
}
|
||||
worldState.Entities["statue"] = world.Entity{ID: "statue", LocationID: "square", State: map[string]interface{}{}}
|
||||
if err := runtime.store.SaveWorldState(worldState); err != nil {
|
||||
t.Fatalf("save world state failed: %v", err)
|
||||
}
|
||||
manager.SetRunFunc(func(ctx context.Context, task *tools.AgentTask) (string, error) {
|
||||
return `{"actor_id":"caretaker","action":"interact","target_entity":"statue","speech":"polishes the statue"}`, nil
|
||||
})
|
||||
if _, err := runtime.Tick(context.Background(), "interact"); err != nil {
|
||||
t.Fatalf("tick failed: %v", err)
|
||||
}
|
||||
snap, err := runtime.Snapshot(10)
|
||||
if err != nil {
|
||||
t.Fatalf("snapshot failed: %v", err)
|
||||
}
|
||||
data, _ := json.Marshal(snap)
|
||||
if !strings.Contains(string(data), `"entity_occupancy":{"square":["statue"]}`) {
|
||||
t.Fatalf("expected entity occupancy for statue, got %s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleRuntimeAdminSnapshotIncludesWorld(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
manager := tools.NewAgentManager(nil, workspace, nil)
|
||||
store := manager.ProfileStore()
|
||||
if store == nil {
|
||||
t.Fatalf("expected profile store")
|
||||
}
|
||||
if _, err := store.Upsert(tools.AgentProfile{
|
||||
AgentID: "watcher",
|
||||
Name: "Watcher",
|
||||
Kind: "npc",
|
||||
Persona: "Keeps watch.",
|
||||
HomeLocation: "commons",
|
||||
DefaultGoals: []string{"watch"},
|
||||
Status: "active",
|
||||
}); err != nil {
|
||||
t.Fatalf("profile upsert failed: %v", err)
|
||||
}
|
||||
manager.SetRunFunc(func(ctx context.Context, task *tools.AgentTask) (string, error) {
|
||||
return `{"actor_id":"watcher","action":"observe","internal_reasoning_summary":"on watch"}`, nil
|
||||
})
|
||||
worldRuntime := NewWorldRuntime(workspace, store, tools.NewAgentDispatcher(manager), manager)
|
||||
loop := &AgentLoop{
|
||||
agentManager: manager,
|
||||
agentDispatcher: tools.NewAgentDispatcher(manager),
|
||||
worldRuntime: worldRuntime,
|
||||
}
|
||||
if _, err := worldRuntime.Tick(context.Background(), "seed"); err != nil {
|
||||
t.Fatalf("world tick failed: %v", err)
|
||||
}
|
||||
|
||||
out, err := loop.HandleRuntimeAdmin(context.Background(), "snapshot", map[string]interface{}{"limit": 10})
|
||||
if err != nil {
|
||||
t.Fatalf("snapshot failed: %v", err)
|
||||
}
|
||||
payload, ok := out.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("unexpected payload type: %T", out)
|
||||
}
|
||||
snapshot, ok := payload["snapshot"].(tools.RuntimeSnapshot)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected snapshot type: %T", payload["snapshot"])
|
||||
}
|
||||
if snapshot.World == nil {
|
||||
t.Fatalf("expected world snapshot in runtime snapshot")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorldRuntimeDelegateSendsMailboxMessage(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
manager := tools.NewAgentManager(nil, workspace, nil)
|
||||
store := manager.ProfileStore()
|
||||
if store == nil {
|
||||
t.Fatalf("expected profile store")
|
||||
}
|
||||
for _, profile := range []tools.AgentProfile{
|
||||
{
|
||||
AgentID: "chief",
|
||||
Name: "Chief",
|
||||
Kind: "npc",
|
||||
Persona: "Delegates work.",
|
||||
HomeLocation: "commons",
|
||||
DefaultGoals: []string{"coordinate"},
|
||||
Status: "active",
|
||||
},
|
||||
{
|
||||
AgentID: "scout",
|
||||
Name: "Scout",
|
||||
Kind: "npc",
|
||||
Persona: "Explores.",
|
||||
HomeLocation: "commons",
|
||||
DefaultGoals: []string{"patrol"},
|
||||
Status: "active",
|
||||
},
|
||||
} {
|
||||
if _, err := store.Upsert(profile); err != nil {
|
||||
t.Fatalf("profile upsert failed: %v", err)
|
||||
}
|
||||
}
|
||||
manager.SetRunFunc(func(ctx context.Context, task *tools.AgentTask) (string, error) {
|
||||
if task.AgentID == "chief" {
|
||||
return `{"actor_id":"chief","action":"delegate","target_agent":"scout","speech":"Check the square."}`, nil
|
||||
}
|
||||
return `{"actor_id":"scout","action":"wait"}`, nil
|
||||
})
|
||||
runtime := NewWorldRuntime(workspace, store, tools.NewAgentDispatcher(manager), manager)
|
||||
|
||||
if _, err := runtime.Tick(context.Background(), "delegate"); err != nil {
|
||||
t.Fatalf("tick failed: %v", err)
|
||||
}
|
||||
msgs, err := manager.Inbox("scout", 10)
|
||||
if err != nil {
|
||||
t.Fatalf("inbox failed: %v", err)
|
||||
}
|
||||
if len(msgs) == 0 {
|
||||
t.Fatalf("expected delegate message in scout inbox")
|
||||
}
|
||||
found := false
|
||||
for _, msg := range msgs {
|
||||
if msg.Type != "delegate" {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(msg.Content, "Check the square") {
|
||||
t.Fatalf("unexpected delegate content: %+v", msg)
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("expected delegate message in inbox, got %+v", msgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleRuntimeAdminWorldActions(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
manager := tools.NewAgentManager(nil, workspace, nil)
|
||||
worldRuntime := NewWorldRuntime(workspace, manager.ProfileStore(), tools.NewAgentDispatcher(manager), manager)
|
||||
loop := &AgentLoop{
|
||||
agentManager: manager,
|
||||
agentDispatcher: tools.NewAgentDispatcher(manager),
|
||||
worldRuntime: worldRuntime,
|
||||
}
|
||||
|
||||
out, err := loop.HandleRuntimeAdmin(context.Background(), "world_npc_create", map[string]interface{}{
|
||||
"npc_id": "merchant",
|
||||
"name": "Merchant",
|
||||
"persona": "Talkative trader",
|
||||
"home_location": "square",
|
||||
"default_goals": []string{"watch trade"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("world_npc_create failed: %v", err)
|
||||
}
|
||||
payload, ok := out.(map[string]interface{})
|
||||
if !ok || strings.TrimSpace(tools.MapStringArg(payload, "npc_id")) != "merchant" {
|
||||
t.Fatalf("unexpected create payload: %#v", out)
|
||||
}
|
||||
out, err = loop.HandleRuntimeAdmin(context.Background(), "world_npc_list", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("world_npc_list failed: %v", err)
|
||||
}
|
||||
listPayload, ok := out.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("unexpected list payload: %T", out)
|
||||
}
|
||||
items, ok := listPayload["items"].([]map[string]interface{})
|
||||
if !ok || len(items) == 0 {
|
||||
t.Fatalf("expected world npc list items, got %#v", listPayload["items"])
|
||||
}
|
||||
out, err = loop.HandleRuntimeAdmin(context.Background(), "world_quest_create", map[string]interface{}{
|
||||
"id": "meet-merchant",
|
||||
"title": "Meet Merchant",
|
||||
"owner_npc_id": "merchant",
|
||||
"summary": "Find the merchant in the square.",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("world_quest_create failed: %v", err)
|
||||
}
|
||||
questPayload, ok := out.(map[string]interface{})
|
||||
if !ok || strings.TrimSpace(tools.MapStringArg(questPayload, "quest_id")) != "meet-merchant" {
|
||||
t.Fatalf("unexpected quest create payload: %#v", out)
|
||||
}
|
||||
out, err = loop.HandleRuntimeAdmin(context.Background(), "world_quest_list", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("world_quest_list failed: %v", err)
|
||||
}
|
||||
listPayload, ok = out.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("unexpected quest list payload: %T", out)
|
||||
}
|
||||
if _, ok := listPayload["items"].([]map[string]interface{}); !ok {
|
||||
t.Fatalf("expected quest list items, got %#v", listPayload["items"])
|
||||
}
|
||||
}
|
||||
|
||||
type worldDecisionStubProvider struct {
|
||||
content string
|
||||
}
|
||||
|
||||
func (p worldDecisionStubProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, options map[string]interface{}) (*providers.LLMResponse, error) {
|
||||
return &providers.LLMResponse{Content: p.content, FinishReason: "stop"}, nil
|
||||
}
|
||||
|
||||
func (p worldDecisionStubProvider) GetDefaultModel() string { return "stub-world-model" }
|
||||
|
||||
func TestRunWorldDecisionTaskUsesLLMJSONWhenAvailable(t *testing.T) {
|
||||
loop := &AgentLoop{
|
||||
provider: worldDecisionStubProvider{
|
||||
content: `{"actor_id":"keeper","action":"speak","speech":"Welcome to the square.","internal_reasoning_summary":"greets newcomers"}`,
|
||||
},
|
||||
}
|
||||
out, err := loop.runWorldDecisionTask(context.Background(), &tools.AgentTask{
|
||||
AgentID: "keeper",
|
||||
RunKind: "world_npc",
|
||||
Task: `{"npc_id":"keeper"}`,
|
||||
WorldDecision: &tools.WorldDecisionContext{
|
||||
NPCSnapshot: map[string]interface{}{
|
||||
"display_name": "Keeper",
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("runWorldDecisionTask failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, `"Welcome to the square."`) {
|
||||
t.Fatalf("expected llm JSON to be used, got %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunWorldDecisionTaskFallsBackWhenLLMOutputInvalid(t *testing.T) {
|
||||
loop := &AgentLoop{
|
||||
provider: worldDecisionStubProvider{
|
||||
content: `not-json at all`,
|
||||
},
|
||||
}
|
||||
out, err := loop.runWorldDecisionTask(context.Background(), &tools.AgentTask{
|
||||
AgentID: "keeper",
|
||||
RunKind: "world_npc",
|
||||
Task: `{"npc_id":"keeper"}`,
|
||||
WorldDecision: &tools.WorldDecisionContext{
|
||||
NPCSnapshot: map[string]interface{}{
|
||||
"display_name": "Keeper",
|
||||
"current_location": "commons",
|
||||
},
|
||||
VisibleEvents: []map[string]interface{}{
|
||||
{
|
||||
"type": "user_input",
|
||||
"location_id": "commons",
|
||||
"content": "I walk into the commons.",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("runWorldDecisionTask failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, `"action":"speak"`) {
|
||||
t.Fatalf("expected fallback speak intent, got %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorldRuntimeHandleUserInputQuestCommands(t *testing.T) {
|
||||
workspace := t.TempDir()
|
||||
manager := tools.NewAgentManager(nil, workspace, nil)
|
||||
runtime := NewWorldRuntime(workspace, manager.ProfileStore(), tools.NewAgentDispatcher(manager), manager)
|
||||
if _, err := runtime.CreateQuest(context.Background(), map[string]interface{}{
|
||||
"id": "meet-merchant",
|
||||
"title": "Meet Merchant",
|
||||
"summary": "Find the merchant in the square.",
|
||||
}); err != nil {
|
||||
t.Fatalf("create quest failed: %v", err)
|
||||
}
|
||||
|
||||
out, err := runtime.HandleUserInput(context.Background(), "查看任务", "cli", "direct")
|
||||
if err != nil {
|
||||
t.Fatalf("list quest input failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "Meet Merchant") {
|
||||
t.Fatalf("expected quest list output, got %q", out)
|
||||
}
|
||||
|
||||
out, err = runtime.HandleUserInput(context.Background(), "接受任务 Meet Merchant", "cli", "direct")
|
||||
if err != nil {
|
||||
t.Fatalf("accept quest input failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "已接受任务") {
|
||||
t.Fatalf("expected accept output, got %q", out)
|
||||
}
|
||||
quest, found, err := runtime.QuestGet("meet-merchant")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("expected quest after accept, found=%v err=%v", found, err)
|
||||
}
|
||||
if got := strings.TrimSpace(fmt.Sprint(quest["status"])); got != "accepted" {
|
||||
t.Fatalf("expected accepted status, got %q", got)
|
||||
}
|
||||
|
||||
out, err = runtime.HandleUserInput(context.Background(), "推进任务 Meet Merchant 已抵达广场", "cli", "direct")
|
||||
if err != nil {
|
||||
t.Fatalf("progress quest input failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "已推进任务") {
|
||||
t.Fatalf("expected progress output, got %q", out)
|
||||
}
|
||||
quest, found, err = runtime.QuestGet("meet-merchant")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("expected quest after progress, found=%v err=%v", found, err)
|
||||
}
|
||||
if got := strings.TrimSpace(fmt.Sprint(quest["status"])); got != "in_progress" {
|
||||
t.Fatalf("expected in_progress status, got %q", got)
|
||||
}
|
||||
|
||||
out, err = runtime.HandleUserInput(context.Background(), "完成任务 Meet Merchant", "cli", "direct")
|
||||
if err != nil {
|
||||
t.Fatalf("complete quest input failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "已完成任务") {
|
||||
t.Fatalf("expected complete output, got %q", out)
|
||||
}
|
||||
quest, found, err = runtime.QuestGet("meet-merchant")
|
||||
if err != nil || !found {
|
||||
t.Fatalf("expected quest after complete, found=%v err=%v", found, err)
|
||||
}
|
||||
if got := strings.TrimSpace(fmt.Sprint(quest["status"])); got != "completed" {
|
||||
t.Fatalf("expected completed status, got %q", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user