Files
ragflow/internal/agent/sandbox/ucloud_agent_sandbox_test.go

270 lines
10 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package sandbox
import (
"context"
"strings"
"testing"
)
func TestUCloudAgentSandboxProvider_ProviderTypeAndLanguages(t *testing.T) {
t.Parallel()
p := newUCloudAgentSandboxProviderFromConfig(map[string]any{})
if p.ProviderType() != ProviderUCloudAgentSandbox {
t.Errorf("ProviderType = %q, want %q", p.ProviderType(), ProviderUCloudAgentSandbox)
}
want := map[string]bool{"python": true, "javascript": true}
got := map[string]bool{}
for _, language := range p.SupportedLanguages() {
if !want[language] {
t.Errorf("unexpected language: %q", language)
}
got[language] = true
}
for language := range want {
if !got[language] {
t.Errorf("missing required language: %q", language)
}
}
}
func TestUCloudAgentSandboxProvider_Defaults(t *testing.T) {
t.Parallel()
p := newUCloudAgentSandboxProviderFromConfig(map[string]any{})
if p.region != ucloudAgentSandboxDefaultRegion {
t.Errorf("region = %q, want %q", p.region, ucloudAgentSandboxDefaultRegion)
}
if p.template != ucloudAgentSandboxDefaultTemplate {
t.Errorf("template = %q, want %q", p.template, ucloudAgentSandboxDefaultTemplate)
}
if p.allowInternetAccess {
t.Error("allowInternetAccess = true, want false by default")
}
if p.insecureHTTP {
t.Error("insecureHTTP = true, want false by default")
}
if p.timeoutSec != ucloudAgentSandboxDefaultTimeout {
t.Errorf("timeoutSec = %d, want %d", p.timeoutSec, ucloudAgentSandboxDefaultTimeout)
}
if p.sandboxTimeoutSec != ucloudAgentSandboxDefaultLifetime {
t.Errorf("sandboxTimeoutSec = %d, want %d", p.sandboxTimeoutSec, ucloudAgentSandboxDefaultLifetime)
}
if p.maxOutputBytes != ucloudAgentSandboxDefaultMaxOutputBytes {
t.Errorf("maxOutputBytes = %d, want %d", p.maxOutputBytes, ucloudAgentSandboxDefaultMaxOutputBytes)
}
if p.maxArtifacts != ucloudAgentSandboxDefaultMaxArtifacts {
t.Errorf("maxArtifacts = %d, want %d", p.maxArtifacts, ucloudAgentSandboxDefaultMaxArtifacts)
}
if p.maxArtifactBytes != ucloudAgentSandboxDefaultMaxArtifactSize {
t.Errorf("maxArtifactBytes = %d, want %d", p.maxArtifactBytes, ucloudAgentSandboxDefaultMaxArtifactSize)
}
}
func TestUCloudAgentSandboxProvider_ConfigOverrides(t *testing.T) {
t.Parallel()
p := newUCloudAgentSandboxProviderFromConfig(map[string]any{
"api_key": "ucloud-key",
"region": "us-ca",
"domain": "sandbox.example.com",
"api_url": "https://api.example.com",
"template": "custom-template",
"allow_internet_access": true,
"insecure_http": true,
"timeout": float64(45),
"sandbox_timeout": float64(600),
"max_output_bytes": float64(2_000_000),
"max_artifacts": float64(50),
"max_artifact_bytes": float64(20_000_000),
})
if p.apiKey != "ucloud-key" {
t.Errorf("apiKey = %q", p.apiKey)
}
if p.region != "us-ca" || p.domain != "sandbox.example.com" || p.apiURL != "https://api.example.com" {
t.Errorf("endpoint config = region:%q domain:%q apiURL:%q", p.region, p.domain, p.apiURL)
}
if p.template != "custom-template" {
t.Errorf("template = %q", p.template)
}
if !p.allowInternetAccess || !p.insecureHTTP {
t.Errorf("boolean overrides not applied: internet=%v insecureHTTP=%v", p.allowInternetAccess, p.insecureHTTP)
}
if p.timeoutSec != 45 || p.sandboxTimeoutSec != 600 {
t.Errorf("timeouts = execution:%d sandbox:%d", p.timeoutSec, p.sandboxTimeoutSec)
}
if p.maxOutputBytes != 2_000_000 || p.maxArtifacts != 50 || p.maxArtifactBytes != 20_000_000 {
t.Errorf("limits = output:%d artifacts:%d artifactBytes:%d", p.maxOutputBytes, p.maxArtifacts, p.maxArtifactBytes)
}
}
func TestUCloudAgentSandboxProvider_EnvOverrides(t *testing.T) {
t.Setenv("UCLOUD_SANDBOX_API_KEY", "env-key")
t.Setenv("UCLOUD_SANDBOX_REGION", "us-ca")
t.Setenv("UCLOUD_SANDBOX_TEMPLATE", "env-template")
t.Setenv("UCLOUD_SANDBOX_ALLOW_INTERNET_ACCESS", "true")
t.Setenv("UCLOUD_SANDBOX_INSECURE_HTTP", "true")
t.Setenv("UCLOUD_SANDBOX_EXECUTION_TIMEOUT", "60")
t.Setenv("UCLOUD_SANDBOX_TIMEOUT", "900")
t.Setenv("UCLOUD_SANDBOX_MAX_OUTPUT_BYTES", "2000")
t.Setenv("UCLOUD_SANDBOX_MAX_ARTIFACTS", "4")
t.Setenv("UCLOUD_SANDBOX_MAX_ARTIFACT_BYTES", "3000")
p := newUCloudAgentSandboxProviderFromEnv()
if p.apiKey != "env-key" || p.region != "us-ca" || p.template != "env-template" {
t.Errorf("env string overrides not applied: key:%q region:%q template:%q", p.apiKey, p.region, p.template)
}
if !p.allowInternetAccess || !p.insecureHTTP {
t.Errorf("env boolean overrides not applied: internet=%v insecureHTTP=%v", p.allowInternetAccess, p.insecureHTTP)
}
if p.timeoutSec != 60 || p.sandboxTimeoutSec != 900 {
t.Errorf("env timeouts = execution:%d sandbox:%d", p.timeoutSec, p.sandboxTimeoutSec)
}
if p.maxOutputBytes != 2000 || p.maxArtifacts != 4 || p.maxArtifactBytes != 3000 {
t.Errorf("env limits = output:%d artifacts:%d artifactBytes:%d", p.maxOutputBytes, p.maxArtifacts, p.maxArtifactBytes)
}
}
func TestUCloudAgentSandboxProvider_Initialize(t *testing.T) {
t.Parallel()
t.Run("missing API key", func(t *testing.T) {
p := newUCloudAgentSandboxProviderFromConfig(map[string]any{})
err := p.Initialize(context.Background())
if err == nil {
t.Fatal("Initialize with no API key: got nil error, want one")
}
if !strings.Contains(err.Error(), "API key") {
t.Errorf("err = %v, want to mention API key", err)
}
})
t.Run("valid local configuration", func(t *testing.T) {
p := newUCloudAgentSandboxProviderFromConfig(map[string]any{"api_key": "fake-key"})
if err := p.Initialize(context.Background()); err != nil {
t.Fatalf("Initialize: %v", err)
}
if !p.isInitialized() || p.client == nil {
t.Error("provider did not retain an initialized SDK client")
}
if err := p.HealthCheck(context.Background()); err != nil {
t.Errorf("HealthCheck after Initialize: %v", err)
}
})
t.Run("invalid limits", func(t *testing.T) {
p := newUCloudAgentSandboxProviderFromConfig(map[string]any{
"api_key": "fake-key",
"max_artifacts": -1,
})
if err := p.Initialize(context.Background()); err == nil {
t.Fatal("Initialize with invalid limits: got nil error, want one")
}
})
}
func TestUCloudAgentSandboxProvider_AllOpsBeforeInit(t *testing.T) {
t.Parallel()
p := newUCloudAgentSandboxProviderFromConfig(map[string]any{})
inst := &SandboxInstance{InstanceID: "x", Provider: ProviderUCloudAgentSandbox}
if _, err := p.CreateInstance(context.Background(), "python"); err == nil {
t.Error("CreateInstance before init: got nil error, want one")
}
if _, err := p.ExecuteCode(context.Background(), inst, "x", "python", 5, nil); err == nil {
t.Error("ExecuteCode before init: got nil error, want one")
}
if err := p.DestroyInstance(context.Background(), inst); err == nil {
t.Error("DestroyInstance before init: got nil error, want one")
}
if err := p.HealthCheck(context.Background()); err == nil {
t.Error("HealthCheck before init: got nil error, want one")
}
}
func TestUCloudAgentSandboxProvider_ExecuteCodeRejectsBadInputs(t *testing.T) {
t.Parallel()
p := newUCloudAgentSandboxProviderFromConfig(map[string]any{"api_key": "fake-key"})
if err := p.Initialize(context.Background()); err != nil {
t.Fatalf("Initialize: %v", err)
}
p.instances["x"] = &ucloudAgentSandboxInstance{}
cases := []struct {
name string
inst *SandboxInstance
lang string
time int
want string
}{
{name: "nil instance", inst: nil, lang: "python", time: 5, want: "instance id"},
{name: "empty instance id", inst: &SandboxInstance{}, lang: "python", time: 5, want: "instance id"},
{name: "unknown instance", inst: &SandboxInstance{InstanceID: "missing"}, lang: "python", time: 5, want: "unknown instance"},
{name: "unsupported language", inst: &SandboxInstance{InstanceID: "x"}, lang: "ruby", time: 5, want: "unsupported language"},
{name: "timeout too small", inst: &SandboxInstance{InstanceID: "x"}, lang: "python", time: 0, want: "timeout"},
{name: "timeout too large", inst: &SandboxInstance{InstanceID: "x"}, lang: "python", time: 1000, want: "timeout"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, err := p.ExecuteCode(context.Background(), tc.inst, "x", tc.lang, tc.time, nil)
if err == nil {
t.Fatalf("got nil error, want one containing %q", tc.want)
}
if !strings.Contains(err.Error(), tc.want) {
t.Errorf("err = %v, want to contain %q", err, tc.want)
}
})
}
}
func TestUCloudAgentSandboxProvider_DestroyInstanceRejectsEmptyID(t *testing.T) {
t.Parallel()
p := newUCloudAgentSandboxProviderFromConfig(map[string]any{"api_key": "fake-key"})
if err := p.Initialize(context.Background()); err != nil {
t.Fatalf("Initialize: %v", err)
}
if err := p.DestroyInstance(context.Background(), nil); err == nil {
t.Error("DestroyInstance(nil): got nil error, want one")
}
if err := p.DestroyInstance(context.Background(), &SandboxInstance{}); err == nil {
t.Error("DestroyInstance(empty id): got nil error, want one")
}
if err := p.DestroyInstance(context.Background(), &SandboxInstance{InstanceID: "already-gone"}); err != nil {
t.Errorf("DestroyInstance(unknown id): %v, want idempotent success", err)
}
}
func TestUCloudAgentSandboxProvider_ProviderTypeStaysDistinct(t *testing.T) {
t.Parallel()
seen := map[ProviderType]bool{}
for _, provider := range []SandboxProvider{
newSelfManagedProviderFromEnv(),
newAliyunProviderFromEnv(),
newE2BProviderFromEnv(),
newTenkiProviderFromEnv(),
newUCloudAgentSandboxProviderFromConfig(map[string]any{}),
} {
if seen[provider.ProviderType()] {
t.Errorf("provider type %q seen twice", provider.ProviderType())
}
seen[provider.ProviderType()] = true
}
}