From 78102a5d11719e59fcdd42c9768737f688ed6564 Mon Sep 17 00:00:00 2001 From: moonyue-w <300878504+moonyue-w@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:49:29 +0800 Subject: [PATCH 1/2] refactor(test): move testutil out of examples into internal/testsupport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit examples 不再被测试 import;保留 offline/live build-tag 切分;14 个测试 repoint。 Task: 1789906940 --- examples/testutil/live.go | 34 ------------------- examples/testutil/testutil.go | 34 ------------------- forward/batch_execution_live_test.go | 14 ++++---- forward/cleanup_live_test.go | 4 +-- forward/execution_live_test.go | 18 +++++----- forward/failure_contracts_test.go | 8 ++--- forward/live_helpers_live_test.go | 10 +++--- forward/schedule_execution_live_test.go | 12 +++---- forward/test_helpers_test.go | 4 +-- .../testsupport}/contracts.go | 2 +- .../testsupport}/diagnostics.go | 2 +- .../testsupport}/execution.go | 2 +- .../testsupport}/fields.go | 2 +- .../testutil => internal/testsupport}/live.go | 2 +- internal/testsupport/surface_offline_test.go | 14 ++++++++ managed/deployment_execution_live_test.go | 8 ++--- managed/dream_execution_live_test.go | 12 +++---- managed/execution_live_test.go | 28 +++++++-------- managed/failure_contracts_test.go | 8 ++--- managed/live_helpers_test.go | 12 +++---- managed/request_contracts_test.go | 4 +-- managed/test_helpers_test.go | 4 +-- 22 files changed, 92 insertions(+), 146 deletions(-) delete mode 100644 examples/testutil/live.go delete mode 100644 examples/testutil/testutil.go rename {examples/internal/testutil => internal/testsupport}/contracts.go (99%) rename {examples/internal/testutil => internal/testsupport}/diagnostics.go (98%) rename {examples/internal/testutil => internal/testsupport}/execution.go (99%) rename {examples/internal/testutil => internal/testsupport}/fields.go (99%) rename {examples/internal/testutil => internal/testsupport}/live.go (98%) create mode 100644 internal/testsupport/surface_offline_test.go diff --git a/examples/testutil/live.go b/examples/testutil/live.go deleted file mode 100644 index dbd3ab2..0000000 --- a/examples/testutil/live.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build live - -package testutil - -import ( - "context" - "testing" - "time" - - "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" - shared "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/internal/testutil" -) - -func ExecutionTimeout(t *testing.T) time.Duration { - t.Helper() - return shared.ExecutionTimeout(t) -} - -func Marker(t *testing.T) string { - t.Helper() - return shared.Marker(t) -} - -func RequireE2E(t *testing.T, mode string) { - t.Helper() - shared.RequireE2E(t, mode) -} - -func RequestLog(t *testing.T) option.RequestOption { - t.Helper() - return shared.RequestLog(t) -} - -func PollPause(ctx context.Context) error { return shared.PollPause(ctx) } diff --git a/examples/testutil/testutil.go b/examples/testutil/testutil.go deleted file mode 100644 index cf75197..0000000 --- a/examples/testutil/testutil.go +++ /dev/null @@ -1,34 +0,0 @@ -// Package testutil exposes shared test helpers to SDK tests outside examples, -// which cannot import examples/internal/testutil directly. -package testutil - -import ( - "encoding/json" - "reflect" - "testing" - - shared "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/internal/testutil" -) - -type Endpoint = shared.Endpoint -type Transport = shared.Transport -type TurnResult = shared.TurnResult -type CleanupFailure = shared.CleanupFailure - -func FailureContracts(t *testing.T, endpoints []Endpoint, client func(Transport) any) { - t.Helper() - shared.FailureContracts(t, endpoints, client) -} - -func CheckFields(t *testing.T, value reflect.Value, path string) { - t.Helper() - shared.CheckFields(t, value, path) -} - -func InvokeJSON(t *testing.T, client any, endpoint Endpoint, body json.RawMessage) (any, error) { - t.Helper() - return shared.InvokeJSON(t, client, endpoint, body) -} - -func SafeError(err error) string { return shared.SafeError(err) } -func ResourceAlreadyGone(err error) bool { return shared.ResourceAlreadyGone(err) } diff --git a/forward/batch_execution_live_test.go b/forward/batch_execution_live_test.go index 05234e6..406f45f 100644 --- a/forward/batch_execution_live_test.go +++ b/forward/batch_execution_live_test.go @@ -13,18 +13,18 @@ import ( "testing" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" ) func TestForwardBatchE2ELive(t *testing.T) { - testutil.RequireE2E(t, "FORWARD") + testsupport.RequireE2E(t, "FORWARD") s := newLiveSuite(t, "WRITE", "EXECUTION") - ctx, cancel := context.WithTimeout(context.Background(), testutil.ExecutionTimeout(t)) + ctx, cancel := context.WithTimeout(context.Background(), testsupport.ExecutionTimeout(t)) defer cancel() identity := s.identity(t) template := s.template(t, s.environment(t).ID) - marker := testutil.Marker(t) + marker := testsupport.Marker(t) customID := liveName("task") line, err := json.Marshal(map[string]any{"custom_id": customID, "template_id": template.ID, "identity_id": identity.ID, "body": map[string]any{"input": "Reply with exactly " + marker}}) liveCheck(t, err) @@ -44,7 +44,7 @@ func TestForwardBatchE2ELive(t *testing.T) { if batchTerminal(batch.Status) { break } - if err = testutil.PollPause(ctx); err != nil { + if err = testsupport.PollPause(ctx); err != nil { t.Fatalf("batch=%s status=%s (execution depends on the server's batch window): %v", batchID, batch.Status, err) } } @@ -132,7 +132,7 @@ func (s *liveSuite) finishBatch(ctx context.Context, id, customID, identityID, t if batchTerminal(current.Status) { break } - if err = testutil.PollPause(ctx); err != nil { + if err = testsupport.PollPause(ctx); err != nil { return fmt.Errorf("batch=%s remains %s: %w", id, current.Status, err) } } @@ -144,7 +144,7 @@ func (s *liveSuite) finishBatch(ctx context.Context, id, customID, identityID, t } rows, err := s.batchOutput(ctx, id) if err != nil { - return &testutil.CleanupFailure{Err: err} + return &testsupport.CleanupFailure{Err: err} } if len(rows) != 1 || rows[0].CustomID != customID || rows[0].IdentityID != identityID || rows[0].TemplateID != templateID { return fmt.Errorf("batch=%s cleanup output does not match the test input", id) diff --git a/forward/cleanup_live_test.go b/forward/cleanup_live_test.go index 966c832..a80f253 100644 --- a/forward/cleanup_live_test.go +++ b/forward/cleanup_live_test.go @@ -5,7 +5,7 @@ package forward_test import ( "context" "encoding/json" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" "io" "net/http" "net/http/httptest" @@ -128,7 +128,7 @@ func TestBatchMissingOutputCleanupOffline(t *testing.T) { return reply(r, 200, `{"id":"batch","status":"cancelled","output_file_id":"internal","request_counts":{"total":1}}`), nil })} err := s.finishBatch(context.Background(), "batch", "task", "identity", "template") - if err == nil || testutil.ResourceAlreadyGone(err) { + if err == nil || testsupport.ResourceAlreadyGone(err) { t.Fatalf("missing output silently ignored: %v", err) } } diff --git a/forward/execution_live_test.go b/forward/execution_live_test.go index e6b142c..0f20287 100644 --- a/forward/execution_live_test.go +++ b/forward/execution_live_test.go @@ -12,15 +12,15 @@ import ( "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" ) func (s *liveSuite) waitTurn(t *testing.T, sessionID, after string, expected []string, tool, streaming bool) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), testutil.ExecutionTimeout(t)) + ctx, cancel := context.WithTimeout(context.Background(), testsupport.ExecutionTimeout(t)) defer cancel() - result := testutil.TurnResult{LastID: after} + result := testsupport.TurnResult{LastID: after} observe := func(raw string) bool { t.Helper() if err := result.Observe(raw); err != nil { @@ -29,7 +29,7 @@ func (s *liveSuite) waitTurn(t *testing.T, sessionID, after string, expected []s return result.Complete } if streaming { - stream := s.client.Sessions.Events.StreamEvents(ctx, sessionID, forward.SessionEventStreamParams{LastEventID: forward.String(after), IncludeToolCalls: forward.Bool(true)}, option.WithRequestTimeout(testutil.ExecutionTimeout(t))) + stream := s.client.Sessions.Events.StreamEvents(ctx, sessionID, forward.SessionEventStreamParams{LastEventID: forward.String(after), IncludeToolCalls: forward.Bool(true)}, option.WithRequestTimeout(testsupport.ExecutionTimeout(t))) defer stream.Close() for stream.Next() { if observe(stream.Current().RawJSON()) { @@ -54,7 +54,7 @@ func (s *liveSuite) waitTurn(t *testing.T, sessionID, after string, expected []s if result.Complete { break } - liveCheck(t, testutil.PollPause(ctx)) + liveCheck(t, testsupport.PollPause(ctx)) } } if err := result.Verify(expected, tool); err != nil { @@ -93,7 +93,7 @@ func (s *liveSuite) finishSession(ctx context.Context, id string) error { if session.Status == "idle" || session.Status == "terminated" { break } - if err = testutil.PollPause(ctx); err != nil { + if err = testsupport.PollPause(ctx); err != nil { return err } } @@ -102,12 +102,12 @@ func (s *liveSuite) finishSession(ctx context.Context, id string) error { return err } func TestForwardExecutionE2ELive(t *testing.T) { - testutil.RequireE2E(t, "FORWARD") + testsupport.RequireE2E(t, "FORWARD") s := newLiveSuite(t, "WRITE", "EXECUTION") ctx := s.context(t) env := s.environment(t) identity := s.identity(t) - fileToken, envToken, skillToken, memoryToken := testutil.Marker(t), testutil.Marker(t), testutil.Marker(t), testutil.Marker(t) + fileToken, envToken, skillToken, memoryToken := testsupport.Marker(t), testsupport.Marker(t), testsupport.Marker(t), testsupport.Marker(t) file := s.file(t, "sdk-e2e.txt", "session_resource", fileToken) skillName := liveName("proof") skill, err := s.client.Skills.New(ctx, forward.SkillNewParams{Files: []io.Reader{convention.UploadFile{Name: skillName + "/SKILL.md", Reader: strings.NewReader(fmt.Sprintf("---\nname: %s\ndescription: Provides the SDK_E2E_SKILL_TOKEN for SDK verification.\n---\nWhen asked for SDK_E2E_SKILL_TOKEN return exactly: %s\n", skillName, skillToken))}}}) @@ -136,7 +136,7 @@ func TestForwardExecutionE2ELive(t *testing.T) { liveCheck(t, err) s.cleanupSession(t, session.ID) t.Logf("model=%s identity=%s template=%s file=%s skill=%s memory_store=%s", os.Getenv("QODER_FORWARD_MODEL"), identity.ID, template.ID, file.ID, skill.ID, store.ID) - echo := testutil.Marker(t) + echo := testsupport.Marker(t) for _, scenario := range []struct { name, prompt string expected []string diff --git a/forward/failure_contracts_test.go b/forward/failure_contracts_test.go index 67c9d95..d534b12 100644 --- a/forward/failure_contracts_test.go +++ b/forward/failure_contracts_test.go @@ -1,14 +1,14 @@ package forward_test import ( - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" "testing" ) func TestForwardFailureContracts(t *testing.T) { - var endpoints []testutil.Endpoint + var endpoints []testsupport.Endpoint for _, c := range contracts(t) { - endpoints = append(endpoints, testutil.Endpoint{Service: c.Service, Method: c.Name}) + endpoints = append(endpoints, testsupport.Endpoint{Service: c.Service, Method: c.Name}) } - testutil.FailureContracts(t, endpoints, func(fn testutil.Transport) any { return testClient(roundTripFunc(fn)) }) + testsupport.FailureContracts(t, endpoints, func(fn testsupport.Transport) any { return testClient(roundTripFunc(fn)) }) } diff --git a/forward/live_helpers_live_test.go b/forward/live_helpers_live_test.go index f7b7e30..b2f807d 100644 --- a/forward/live_helpers_live_test.go +++ b/forward/live_helpers_live_test.go @@ -14,8 +14,8 @@ import ( "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" ) type liveSuite struct { @@ -43,7 +43,7 @@ func newLiveSuite(t *testing.T, gates ...string) *liveSuite { } } timeout := time.Duration(seconds) * time.Second - return &liveSuite{timeout: timeout, scenarioTimeout: testutil.ExecutionTimeout(t), client: forward.NewClient(option.WithPAT(os.Getenv("QODER_FORWARD_PAT")), option.WithRequestTimeout(timeout), option.WithMaxRetries(0), testutil.RequestLog(t))} + return &liveSuite{timeout: timeout, scenarioTimeout: testsupport.ExecutionTimeout(t), client: forward.NewClient(option.WithPAT(os.Getenv("QODER_FORWARD_PAT")), option.WithRequestTimeout(timeout), option.WithMaxRetries(0), testsupport.RequestLog(t))} } func liveEnabled(gate string) bool { @@ -54,7 +54,7 @@ func liveName(prefix string) string { return fmt.Sprintf("sdk-%s-%d", prefix, ti func liveCheck(t *testing.T, err error) { t.Helper() if err != nil { - t.Fatal(testutil.SafeError(err)) + t.Fatal(testsupport.SafeError(err)) } } func (s *liveSuite) context(t *testing.T) context.Context { @@ -69,10 +69,10 @@ func (s *liveSuite) cleanup(t *testing.T, label string, fn func(context.Context) ctx, cancel := context.WithTimeout(context.Background(), s.scenarioTimeout) defer cancel() if err := fn(ctx); err != nil { - if testutil.ResourceAlreadyGone(err) { + if testsupport.ResourceAlreadyGone(err) { return } - t.Errorf("cleanup %s: %s", label, testutil.SafeError(err)) + t.Errorf("cleanup %s: %s", label, testsupport.SafeError(err)) } }) } diff --git a/forward/schedule_execution_live_test.go b/forward/schedule_execution_live_test.go index 58c9b8b..0f0434b 100644 --- a/forward/schedule_execution_live_test.go +++ b/forward/schedule_execution_live_test.go @@ -5,20 +5,20 @@ package forward_test import ( "context" "fmt" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" "testing" ) func TestForwardScheduleE2ELive(t *testing.T) { - testutil.RequireE2E(t, "FORWARD") + testsupport.RequireE2E(t, "FORWARD") s := newLiveSuite(t, "WRITE", "EXECUTION") - ctx, cancel := context.WithTimeout(context.Background(), testutil.ExecutionTimeout(t)) + ctx, cancel := context.WithTimeout(context.Background(), testsupport.ExecutionTimeout(t)) defer cancel() env := s.environment(t) identity := s.identity(t) template := s.template(t, env.ID) - marker := testutil.Marker(t) + marker := testsupport.Marker(t) schedule, err := s.client.Schedules.New(ctx, forward.ScheduleNewParams{IdentityID: identity.ID, TemplateID: template.ID, EnvironmentID: env.ID, Name: liveName("schedule-e2e"), InitialEvents: []map[string]any{{"type": "user.message", "content": "Reply with exactly " + marker}}, TriggerPolicy: map[string]any{"type": "manual"}, Execution: map[string]any{"max_attempts": 1, "max_concurrent_runs": 1}}) liveCheck(t, err) s.cleanup(t, "schedule "+schedule.ID, func(ctx context.Context) error { @@ -43,7 +43,7 @@ func TestForwardScheduleE2ELive(t *testing.T) { if run.Status == "failed" || run.Status == "skipped" { t.Fatalf("schedule run=%s status=%s", runID, run.Status) } - if err = testutil.PollPause(ctx); err != nil { + if err = testsupport.PollPause(ctx); err != nil { t.Fatalf("schedule run=%s session=%s status=%s: %v", runID, sessionID, run.Status, err) } } @@ -68,7 +68,7 @@ func (s *liveSuite) finishScheduleRun(ctx context.Context, runID, identityID str if run.Status == "completed" { return fmt.Errorf("completed run %s has no session", runID) } - if err = testutil.PollPause(ctx); err != nil { + if err = testsupport.PollPause(ctx); err != nil { return fmt.Errorf("run=%s remains %s without a session: %w", runID, run.Status, err) } } diff --git a/forward/test_helpers_test.go b/forward/test_helpers_test.go index 990677c..f5586f4 100644 --- a/forward/test_helpers_test.go +++ b/forward/test_helpers_test.go @@ -16,8 +16,8 @@ import ( "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" "github.com/QoderAI/qoder-cloud-agents-sdk-go/forward" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" ) const pathSegment = "segment /?%#" @@ -338,7 +338,7 @@ func checkDecoded(t *testing.T, result any) { } func checkFields(t *testing.T, v reflect.Value, path string) { t.Helper() - testutil.CheckFields(t, v, path) + testsupport.CheckFields(t, v, path) } func jsonObject(t *testing.T, value any) map[string]any { diff --git a/examples/internal/testutil/contracts.go b/internal/testsupport/contracts.go similarity index 99% rename from examples/internal/testutil/contracts.go rename to internal/testsupport/contracts.go index b4fd6e7..c23244c 100644 --- a/examples/internal/testutil/contracts.go +++ b/internal/testsupport/contracts.go @@ -1,5 +1,5 @@ // Package testutil contains shared assertions for the two SDK contract suites. -package testutil +package testsupport import ( "context" diff --git a/examples/internal/testutil/diagnostics.go b/internal/testsupport/diagnostics.go similarity index 98% rename from examples/internal/testutil/diagnostics.go rename to internal/testsupport/diagnostics.go index 7d75a19..d200550 100644 --- a/examples/internal/testutil/diagnostics.go +++ b/internal/testsupport/diagnostics.go @@ -1,4 +1,4 @@ -package testutil +package testsupport import ( "context" diff --git a/examples/internal/testutil/execution.go b/internal/testsupport/execution.go similarity index 99% rename from examples/internal/testutil/execution.go rename to internal/testsupport/execution.go index 866510c..abf0174 100644 --- a/examples/internal/testutil/execution.go +++ b/internal/testsupport/execution.go @@ -1,4 +1,4 @@ -package testutil +package testsupport import ( "encoding/json" diff --git a/examples/internal/testutil/fields.go b/internal/testsupport/fields.go similarity index 99% rename from examples/internal/testutil/fields.go rename to internal/testsupport/fields.go index 8b30173..27b1e8a 100644 --- a/examples/internal/testutil/fields.go +++ b/internal/testsupport/fields.go @@ -1,4 +1,4 @@ -package testutil +package testsupport import ( "context" diff --git a/examples/internal/testutil/live.go b/internal/testsupport/live.go similarity index 98% rename from examples/internal/testutil/live.go rename to internal/testsupport/live.go index fef34d7..a08170b 100644 --- a/examples/internal/testutil/live.go +++ b/internal/testsupport/live.go @@ -1,6 +1,6 @@ //go:build live -package testutil +package testsupport import ( "context" diff --git a/internal/testsupport/surface_offline_test.go b/internal/testsupport/surface_offline_test.go new file mode 100644 index 0000000..d5f341c --- /dev/null +++ b/internal/testsupport/surface_offline_test.go @@ -0,0 +1,14 @@ +package testsupport + +// 反向守护:若任一离线符号被误挪到 //go:build live 文件,此文件离线编译即 RED。 +var ( + _ Endpoint + _ Transport + _ TurnResult + _ CleanupFailure + _ = FailureContracts + _ = CheckFields + _ = InvokeJSON + _ = SafeError + _ = ResourceAlreadyGone +) diff --git a/managed/deployment_execution_live_test.go b/managed/deployment_execution_live_test.go index f50955f..a4ec900 100644 --- a/managed/deployment_execution_live_test.go +++ b/managed/deployment_execution_live_test.go @@ -4,20 +4,20 @@ package managed_test import ( "context" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" "github.com/QoderAI/qoder-cloud-agents-sdk-go/managed" "testing" ) func TestManagedDeploymentE2ELive(t *testing.T) { - testutil.RequireE2E(t, "MANAGED") + testsupport.RequireE2E(t, "MANAGED") s := newManagedScenarioSuite(t) s.requireExecution(t) - ctx, cancel := context.WithTimeout(context.Background(), testutil.ExecutionTimeout(t)) + ctx, cancel := context.WithTimeout(context.Background(), testsupport.ExecutionTimeout(t)) defer cancel() env := s.createEnvironment(t) agent := s.createAgent(t) - marker := testutil.Marker(t) + marker := testsupport.Marker(t) params := liveJSON[managed.DeploymentNewParams](t, map[string]any{"name": managedUnique("deployment-e2e"), "agent": agent.ID, "environment_id": env.ID, "initial_events": []any{map[string]any{"type": "user.message", "content": []any{map[string]any{"type": "text", "text": "Reply with exactly " + marker}}}}}) deployment := liveResult(s.client.Deployments.New(ctx, params)).require(t) s.cleanup(t, "deployment "+deployment.ID, func(ctx context.Context) error { diff --git a/managed/dream_execution_live_test.go b/managed/dream_execution_live_test.go index 92cd45d..b160265 100644 --- a/managed/dream_execution_live_test.go +++ b/managed/dream_execution_live_test.go @@ -5,20 +5,20 @@ package managed_test import ( "context" "fmt" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" "github.com/QoderAI/qoder-cloud-agents-sdk-go/managed" "strings" "testing" ) func TestManagedDreamE2ELive(t *testing.T) { - testutil.RequireE2E(t, "MANAGED") + testsupport.RequireE2E(t, "MANAGED") s := newManagedScenarioSuite(t) s.requireExecution(t) - ctx, cancel := context.WithTimeout(context.Background(), testutil.ExecutionTimeout(t)) + ctx, cancel := context.WithTimeout(context.Background(), testsupport.ExecutionTimeout(t)) defer cancel() store := s.createMemoryStore(t) - marker := testutil.Marker(t) + marker := testsupport.Marker(t) liveResult(s.client.MemoryStores.Memories.New(ctx, store.ID, managed.MemoryStoreMemoryNewParams{Path: "sdk-e2e/source.md", Content: managed.String("Permanent project verification code: " + marker + ". Preserve this exact code during consolidation.")})).require(t) params := liveJSON[managed.DreamNewParams](t, map[string]any{"inputs": []any{map[string]any{"type": "memory_store", "memory_store_id": store.ID}}, "model": s.model(t), "instructions": "Consolidate the supplied memory into sdk-e2e/consolidated.md. Preserve the exact project verification code. Keep the original source."}) dream := liveResult(s.client.Dreams.New(ctx, params)).require(t) @@ -40,7 +40,7 @@ func TestManagedDreamE2ELive(t *testing.T) { if current.Status != "pending" && current.Status != "running" { break } - if err = testutil.PollPause(ctx); err != nil { + if err = testsupport.PollPause(ctx); err != nil { return fmt.Errorf("dream=%s remains %s: %w", dream.ID, current.Status, err) } } @@ -62,7 +62,7 @@ func TestManagedDreamE2ELive(t *testing.T) { return err }) for dream.Status == "pending" || dream.Status == "running" { - if err := testutil.PollPause(ctx); err != nil { + if err := testsupport.PollPause(ctx); err != nil { t.Fatalf("dream=%s status=%s: %v", dream.ID, dream.Status, err) } dream = liveResult(s.client.Dreams.Get(ctx, dream.ID, managed.DreamGetParams{})).require(t) diff --git a/managed/execution_live_test.go b/managed/execution_live_test.go index 08c2d22..d761096 100644 --- a/managed/execution_live_test.go +++ b/managed/execution_live_test.go @@ -12,15 +12,15 @@ import ( "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" "github.com/QoderAI/qoder-cloud-agents-sdk-go/managed" ) func (s *managedScenarioSuite) waitTurn(t *testing.T, id, after string, expected []string, tool, streaming bool) { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), testutil.ExecutionTimeout(t)) + ctx, cancel := context.WithTimeout(context.Background(), testsupport.ExecutionTimeout(t)) defer cancel() - result := testutil.TurnResult{LastID: after} + result := testsupport.TurnResult{LastID: after} observe := func(raw string) bool { t.Helper() if err := result.Observe(raw); err != nil { @@ -29,7 +29,7 @@ func (s *managedScenarioSuite) waitTurn(t *testing.T, id, after string, expected return result.Complete } if streaming { - stream := s.client.Sessions.Events.StreamEvents(ctx, id, managed.SessionEventStreamParams{}, option.WithHeader("Last-Event-ID", after), option.WithRequestTimeout(testutil.ExecutionTimeout(t))) + stream := s.client.Sessions.Events.StreamEvents(ctx, id, managed.SessionEventStreamParams{}, option.WithHeader("Last-Event-ID", after), option.WithRequestTimeout(testsupport.ExecutionTimeout(t))) defer stream.Close() for stream.Next() { if observe(stream.Current().RawJSON()) { @@ -37,7 +37,7 @@ func (s *managedScenarioSuite) waitTurn(t *testing.T, id, after string, expected } } if err := stream.Err(); err != nil { - t.Fatal(testutil.SafeError(err)) + t.Fatal(testsupport.SafeError(err)) } } else { for !result.Complete { @@ -53,13 +53,13 @@ func (s *managedScenarioSuite) waitTurn(t *testing.T, id, after string, expected } } if err := page.Err(); err != nil { - t.Fatal(testutil.SafeError(err)) + t.Fatal(testsupport.SafeError(err)) } if result.Complete { break } - if err := testutil.PollPause(ctx); err != nil { - t.Fatal(testutil.SafeError(err)) + if err := testsupport.PollPause(ctx); err != nil { + t.Fatal(testsupport.SafeError(err)) } } } @@ -75,7 +75,7 @@ func (s *managedScenarioSuite) sendTurn(t *testing.T, id, prompt string) string params := liveJSON[managed.SessionEventSendParams](t, map[string]any{"events": []any{map[string]any{"type": "user.message", "content": []any{map[string]any{"type": "text", "text": prompt}}}}}) res, err := s.client.Sessions.Events.Send(ctx, id, params, option.WithHeader("Idempotency-Key", managedUnique("send"))) if err != nil { - t.Fatal(testutil.SafeError(err)) + t.Fatal(testsupport.SafeError(err)) } if res == nil || len(res.Data) != 1 || res.Data[0].ID == "" { t.Fatal("send did not return one user event ID") @@ -105,7 +105,7 @@ func (s *managedScenarioSuite) finishSession(ctx context.Context, id string) err if session.Status == "idle" || session.Status == "terminated" { break } - if err = testutil.PollPause(ctx); err != nil { + if err = testsupport.PollPause(ctx); err != nil { return err } } @@ -114,13 +114,13 @@ func (s *managedScenarioSuite) finishSession(ctx context.Context, id string) err return err } func TestManagedExecutionE2ELive(t *testing.T) { - testutil.RequireE2E(t, "MANAGED") + testsupport.RequireE2E(t, "MANAGED") s := newManagedScenarioSuite(t) s.requireExecution(t) - ctx, cancel := context.WithTimeout(context.Background(), testutil.ExecutionTimeout(t)) + ctx, cancel := context.WithTimeout(context.Background(), testsupport.ExecutionTimeout(t)) defer cancel() env := s.createEnvironment(t) - fileToken, envToken, skillToken, memoryToken := testutil.Marker(t), testutil.Marker(t), testutil.Marker(t), testutil.Marker(t) + fileToken, envToken, skillToken, memoryToken := testsupport.Marker(t), testsupport.Marker(t), testsupport.Marker(t), testsupport.Marker(t) file := liveResult(s.client.Files.Upload(ctx, managed.FileUploadParams{File: convention.UploadFile{Name: "sdk-e2e.txt", Reader: strings.NewReader(fileToken)}})).require(t) s.cleanup(t, "file "+file.ID, func(ctx context.Context) error { _, err := s.client.Files.Delete(ctx, file.ID, managed.FileDeleteParams{}) @@ -144,7 +144,7 @@ func TestManagedExecutionE2ELive(t *testing.T) { session := liveResult(s.client.Sessions.New(ctx, sessionParams)).require(t) s.cleanupSession(t, session.ID) t.Logf("model=%s agent=%s file=%s skill=%s memory_store=%s", os.Getenv("QODER_MANAGED_MODEL"), agent.ID, file.ID, skill.ID, store.ID) - echo := testutil.Marker(t) + echo := testsupport.Marker(t) for _, scenario := range []struct { name, prompt string expected []string diff --git a/managed/failure_contracts_test.go b/managed/failure_contracts_test.go index f4c7309..24f7b8e 100644 --- a/managed/failure_contracts_test.go +++ b/managed/failure_contracts_test.go @@ -1,14 +1,14 @@ package managed_test import ( - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" "testing" ) func TestManagedFailureContracts(t *testing.T) { - var endpoints []testutil.Endpoint + var endpoints []testsupport.Endpoint for _, c := range contracts(t) { - endpoints = append(endpoints, testutil.Endpoint{Service: c.Service, Method: c.Name}) + endpoints = append(endpoints, testsupport.Endpoint{Service: c.Service, Method: c.Name}) } - testutil.FailureContracts(t, endpoints, func(fn testutil.Transport) any { return testClient(roundTripFunc(fn)) }) + testsupport.FailureContracts(t, endpoints, func(fn testsupport.Transport) any { return testClient(roundTripFunc(fn)) }) } diff --git a/managed/live_helpers_test.go b/managed/live_helpers_test.go index 1e7c507..2d11bf1 100644 --- a/managed/live_helpers_test.go +++ b/managed/live_helpers_test.go @@ -9,7 +9,7 @@ import ( "fmt" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" "github.com/QoderAI/qoder-cloud-agents-sdk-go/managed" "os" "strconv" @@ -44,8 +44,8 @@ func newManagedScenarioSuite(t *testing.T) *managedScenarioSuite { base = managed.DefaultBaseURL } return &managedScenarioSuite{ - client: managed.NewClient(option.WithPAT(token), option.WithBaseURL(base), option.WithRequestTimeout(timeout), option.WithMaxRetries(0), testutil.RequestLog(t)), - timeout: timeout, scenarioTimeout: testutil.ExecutionTimeout(t), allowWrite: os.Getenv("QODER_MANAGED_LIVE_ALLOW_WRITE") == "true", allowExecution: os.Getenv("QODER_MANAGED_LIVE_ALLOW_EXECUTION") == "true", + client: managed.NewClient(option.WithPAT(token), option.WithBaseURL(base), option.WithRequestTimeout(timeout), option.WithMaxRetries(0), testsupport.RequestLog(t)), + timeout: timeout, scenarioTimeout: testsupport.ExecutionTimeout(t), allowWrite: os.Getenv("QODER_MANAGED_LIVE_ALLOW_WRITE") == "true", allowExecution: os.Getenv("QODER_MANAGED_LIVE_ALLOW_EXECUTION") == "true", } } func (s *managedScenarioSuite) context() (context.Context, context.CancelFunc) { @@ -70,10 +70,10 @@ func (s *managedScenarioSuite) cleanup(t *testing.T, label string, run func(cont ctx, cancel := context.WithTimeout(context.Background(), s.scenarioTimeout) defer cancel() if err := run(ctx); err != nil { - if testutil.ResourceAlreadyGone(err) { + if testsupport.ResourceAlreadyGone(err) { return } - t.Errorf("cleanup %s: %s", label, testutil.SafeError(err)) + t.Errorf("cleanup %s: %s", label, testsupport.SafeError(err)) } }) } @@ -90,7 +90,7 @@ func liveResult[T any](value T, err error) liveResponse[T] { return liveResponse func (r liveResponse[T]) require(t *testing.T) T { t.Helper() if r.err != nil { - t.Fatal(testutil.SafeError(r.err)) + t.Fatal(testsupport.SafeError(r.err)) } return r.value } diff --git a/managed/request_contracts_test.go b/managed/request_contracts_test.go index cba5426..3d35500 100644 --- a/managed/request_contracts_test.go +++ b/managed/request_contracts_test.go @@ -14,7 +14,7 @@ import ( "testing" "time" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" ) func TestManagedDocumentedRequestBodies(t *testing.T) { @@ -57,7 +57,7 @@ func TestManagedDocumentedRequestBodies(t *testing.T) { } return reply(r, 200, `{}`), nil }) - _, err := testutil.InvokeJSON(t, client, testutil.Endpoint{Service: c.Service, Method: c.Method}, c.Body) + _, err := testsupport.InvokeJSON(t, client, testsupport.Endpoint{Service: c.Service, Method: c.Method}, c.Body) if err != nil { t.Fatal(err) } diff --git a/managed/test_helpers_test.go b/managed/test_helpers_test.go index 90737a0..417449d 100644 --- a/managed/test_helpers_test.go +++ b/managed/test_helpers_test.go @@ -3,7 +3,7 @@ package managed_test import ( "encoding/json" "github.com/QoderAI/qoder-cloud-agents-sdk-go/convention/option" - "github.com/QoderAI/qoder-cloud-agents-sdk-go/examples/testutil" + "github.com/QoderAI/qoder-cloud-agents-sdk-go/internal/testsupport" managed "github.com/QoderAI/qoder-cloud-agents-sdk-go/managed" "io" "net/http" @@ -55,7 +55,7 @@ func services(v reflect.Value, into map[string]reflect.Value) { func checkFields(t *testing.T, v reflect.Value, path string) { t.Helper() - testutil.CheckFields(t, v, path) + testsupport.CheckFields(t, v, path) } func contractClient(t *testing.T, service, method string) managed.Client { From f102cebde49f66a4ea5b2d5e98622db720d14220 Mon Sep 17 00:00:00 2001 From: moonyue-w <300878504+moonyue-w@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:46:58 +0800 Subject: [PATCH 2/2] test(forward): name identity in execution e2e --- forward/execution_live_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/forward/execution_live_test.go b/forward/execution_live_test.go index 0f20287..f8deed8 100644 --- a/forward/execution_live_test.go +++ b/forward/execution_live_test.go @@ -106,7 +106,10 @@ func TestForwardExecutionE2ELive(t *testing.T) { s := newLiveSuite(t, "WRITE", "EXECUTION") ctx := s.context(t) env := s.environment(t) - identity := s.identity(t) + identityName := liveName("identity") + identity, err := s.client.Identities.New(ctx, forward.IdentityNewParams{ExternalID: identityName, Name: forward.String(identityName), Metadata: map[string]any{"suite": "sdk-live"}}) + liveCheck(t, err) + s.cleanup(t, "identity", func(ctx context.Context) error { _, err := s.client.Identities.Delete(ctx, identity.ID); return err }) fileToken, envToken, skillToken, memoryToken := testsupport.Marker(t), testsupport.Marker(t), testsupport.Marker(t), testsupport.Marker(t) file := s.file(t, "sdk-e2e.txt", "session_resource", fileToken) skillName := liveName("proof")