106 lines
3.7 KiB
Go
106 lines
3.7 KiB
Go
package service
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type pluginFailingRequestBody struct{}
|
|
|
|
func (pluginFailingRequestBody) Read([]byte) (int, error) {
|
|
return 0, errors.New("测试请求体读取失败")
|
|
}
|
|
func (pluginFailingRequestBody) Close() error { return nil }
|
|
|
|
func TestPluginRuntimeIntegration(t *testing.T) {
|
|
packagePath := os.Getenv("SUB2API_TEST_PLUGIN_PACKAGE")
|
|
if packagePath == "" {
|
|
t.Skip("未提供 SUB2API_TEST_PLUGIN_PACKAGE,跳过本地插件进程集成测试")
|
|
}
|
|
packageFile, err := os.Open(packagePath)
|
|
require.NoError(t, err)
|
|
defer func() { _ = packageFile.Close() }()
|
|
|
|
root := t.TempDir()
|
|
cfg := testPluginConfig(root, false)
|
|
installer := NewPluginPackageInstaller(cfg, PluginHostInfo{Version: "0.1.179", BuildType: "release"})
|
|
installation, err := installer.Install(context.Background(), packageFile, nil)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, PluginSignatureTrusted, installation.SignatureStatus)
|
|
for _, relative := range []string{
|
|
"ui/index.html",
|
|
"ui/assets/styles.css",
|
|
"ui/assets/bridge-v1.js",
|
|
"ui/assets/app.js",
|
|
} {
|
|
assert.FileExists(t, filepath.Join(installation.InstallPath, filepath.FromSlash(relative)))
|
|
}
|
|
|
|
runtime, err := startPluginRuntime(context.Background(), installation, 10*time.Second, filepath.Join(root, "runtime"))
|
|
require.NoError(t, err)
|
|
defer runtime.kill()
|
|
require.NoError(t, runtime.validateAndApplyConfig(context.Background(), []byte(`{
|
|
"request_timeout_seconds":30,
|
|
"response_header_timeout_seconds":10,
|
|
"idle_connection_timeout_seconds":30,
|
|
"max_idle_connections":10,
|
|
"max_idle_connections_per_host":5,
|
|
"enable_http2":true,
|
|
"tls_min_version":"1.2",
|
|
"proxy_mode":"disabled",
|
|
"extra_headers":{"X-Plugin-Test":"enabled"}
|
|
}`)))
|
|
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
body, readErr := io.ReadAll(request.Body)
|
|
require.NoError(t, readErr)
|
|
assert.Equal(t, "payload", string(body))
|
|
assert.Equal(t, "enabled", request.Header.Get("X-Plugin-Test"))
|
|
assert.Equal(t, int64(len("payload")), request.ContentLength)
|
|
assert.Empty(t, request.TransferEncoding)
|
|
writer.Header().Set("X-Upstream", "plugin-runtime")
|
|
writer.WriteHeader(http.StatusCreated)
|
|
_, _ = writer.Write([]byte("response-through-plugin"))
|
|
}))
|
|
defer upstream.Close()
|
|
|
|
request, err := http.NewRequestWithContext(context.Background(), http.MethodPost, upstream.URL, bytes.NewBufferString("payload"))
|
|
require.NoError(t, err)
|
|
request.Header.Set("Content-Type", "application/json")
|
|
account := &Account{ID: 42, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Concurrency: 1}
|
|
require.True(t, runtime.beginRequest())
|
|
response, err := runtime.roundTrip(context.Background(), request, "", account)
|
|
if err != nil {
|
|
runtime.finishRequest()
|
|
}
|
|
require.NoError(t, err)
|
|
body, err := io.ReadAll(response.Body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, http.StatusCreated, response.StatusCode)
|
|
assert.Equal(t, "plugin-runtime", response.Header.Get("X-Upstream"))
|
|
assert.Equal(t, "response-through-plugin", string(body))
|
|
assert.Equal(t, int64(len("response-through-plugin")), response.ContentLength)
|
|
require.NoError(t, response.Body.Close())
|
|
|
|
failingRequest, err := http.NewRequestWithContext(context.Background(), http.MethodPost, upstream.URL, pluginFailingRequestBody{})
|
|
require.NoError(t, err)
|
|
failingRequest.ContentLength = -1
|
|
require.True(t, runtime.beginRequest())
|
|
started := time.Now()
|
|
_, err = runtime.roundTrip(context.Background(), failingRequest, "", account)
|
|
runtime.finishRequest()
|
|
require.Error(t, err)
|
|
assert.Less(t, time.Since(started), 2*time.Second)
|
|
}
|