production runtime and mock removal
This commit is contained in:
1 parent
2ef3da7af5
commit
6480e503eb
55 files changed
+581
-540
No files matched your search
@@ -79,6 +79,11 @@ func New(options Options) (*App, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config.Backend == postgres.BackendPostgres && options.ProviderRegistry == nil {
|
||||
if err := validateProductionProviderConfiguration(getenv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
database, err := postgres.Open(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -3,6 +3,7 @@ package application_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/application"
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/assets"
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/httpapi"
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity"
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/jobs"
|
||||
@@ -335,7 +337,7 @@ func TestLocalApplicationBusinessModulesDoNotUseUnavailablePostgresShell(t *test
|
||||
{http.MethodGet, "/api/assets", "", http.StatusOK},
|
||||
{http.MethodGet, "/api/image-templates", "", http.StatusOK},
|
||||
{http.MethodGet, "/api/usage", "", http.StatusOK},
|
||||
{http.MethodPost, "/api/generations/image", `{"prompt":"local mock"}`, http.StatusAccepted},
|
||||
{http.MethodPost, "/api/generations/image", `{"prompt":"local production path"}`, http.StatusAccepted},
|
||||
} {
|
||||
req := httptest.NewRequest(test.method, test.path, strings.NewReader(test.body))
|
||||
if test.body != "" {
|
||||
@@ -349,20 +351,17 @@ func TestLocalApplicationBusinessModulesDoNotUseUnavailablePostgresShell(t *test
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalApplicationMockJobReachesSucceededWithStoredOutput(t *testing.T) {
|
||||
func TestLocalApplicationRealProviderJobReachesSucceededWithStoredOutput(t *testing.T) {
|
||||
app, err := application.New(application.Options{
|
||||
Getenv: applicationEnv(map[string]string{
|
||||
"ZHINIAN_DATA_BACKEND": "local", "ZHINIAN_BILLING_REQUIRED": "0",
|
||||
"ZHINIAN_DATA_BACKEND": "local", "ZHINIAN_BILLING_REQUIRED": "0", "ZHINIAN_RUNTIME_DIR": t.TempDir(),
|
||||
"ZHINIAN_INTERNAL_WORKER_TOKEN": "worker-secret",
|
||||
"ZHINIAN_WORKER_POLL_INTERVAL_MS": "1",
|
||||
}),
|
||||
ProviderRegistry: jobs.ProviderRegistry{
|
||||
"volcengine-visual": providers.NewMock("local-e2e"),
|
||||
"evolink": providers.NewMock("local-e2e"),
|
||||
"bailian": providers.NewMock("local-e2e"),
|
||||
"seedance": providers.NewMock("local-e2e"),
|
||||
"mock": providers.NewMock("local-e2e"),
|
||||
"volcengine-visual": applicationTestProvider{},
|
||||
},
|
||||
RemoteFetcher: applicationTestRemoteFetcher{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New() error = %v", err)
|
||||
@@ -419,6 +418,23 @@ func TestLocalApplicationMockJobReachesSucceededWithStoredOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type applicationTestProvider struct{}
|
||||
|
||||
func (applicationTestProvider) Submit(context.Context, providers.Request) (providers.Result, error) {
|
||||
return providers.Result{TaskID: "provider-task-1", Status: providers.StatusQueued}, nil
|
||||
}
|
||||
|
||||
func (applicationTestProvider) Query(_ context.Context, taskID string) (providers.Result, error) {
|
||||
return providers.Result{TaskID: taskID, Status: providers.StatusSucceeded, OutputURLs: []string{"https://provider.test/generated.png"}}, nil
|
||||
}
|
||||
|
||||
type applicationTestRemoteFetcher struct{}
|
||||
|
||||
func (applicationTestRemoteFetcher) Fetch(context.Context, string) (assets.Blob, error) {
|
||||
content := "real provider output fixture"
|
||||
return assets.Blob{Body: io.NopCloser(strings.NewReader(content)), ContentType: "image/png", Size: int64(len(content))}, nil
|
||||
}
|
||||
|
||||
func TestApplicationComposesPasswordLoginAndLogoutHandlers(t *testing.T) {
|
||||
secret := "application-password-login-secret-with-enough-entropy"
|
||||
authenticator := &applicationCredentialAuthenticator{account: identity.LoginAccount{
|
||||
|
||||
@@ -86,48 +86,21 @@ func videoEngine(getenv postgres.Getenv) string {
|
||||
return "bailian"
|
||||
}
|
||||
|
||||
func mockEnabled(getenv postgres.Getenv, flag string, configured bool) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(getenv(flag))) {
|
||||
case "1", "true":
|
||||
return true
|
||||
case "0", "false":
|
||||
return false
|
||||
default:
|
||||
return !configured
|
||||
}
|
||||
}
|
||||
|
||||
func imageProvider(getenv postgres.Getenv) string {
|
||||
switch imageEngine(getenv) {
|
||||
case "evolink":
|
||||
if mockEnabled(getenv, "EVOLINK_MOCK", strings.TrimSpace(getenv("EVOLINK_API_KEY")) != "") {
|
||||
return "mock"
|
||||
}
|
||||
return "evolink"
|
||||
case "bailian":
|
||||
if mockEnabled(getenv, "BAILIAN_MOCK", bailianAPIKey(getenv) != "") {
|
||||
return "mock"
|
||||
}
|
||||
return "bailian"
|
||||
default:
|
||||
configured := strings.TrimSpace(getenv("VOLCENGINE_ACCESS_KEY_ID")) != "" && strings.TrimSpace(getenv("VOLCENGINE_SECRET_ACCESS_KEY")) != ""
|
||||
if mockEnabled(getenv, "JIMENG_VISUAL_MOCK", configured) {
|
||||
return "mock"
|
||||
}
|
||||
return "volcengine-visual"
|
||||
}
|
||||
}
|
||||
|
||||
func videoProvider(getenv postgres.Getenv) string {
|
||||
if videoEngine(getenv) == "seedance" {
|
||||
if mockEnabled(getenv, "SEEDANCE_MOCK", strings.TrimSpace(getenv("SEEDANCE_API_KEY")) != "") {
|
||||
return "mock"
|
||||
}
|
||||
return "seedance"
|
||||
}
|
||||
if mockEnabled(getenv, "BAILIAN_MOCK", bailianAPIKey(getenv) != "") {
|
||||
return "mock"
|
||||
}
|
||||
return "bailian"
|
||||
}
|
||||
|
||||
@@ -167,7 +140,6 @@ func buildProviderRegistry(getenv postgres.Getenv) jobs.ProviderRegistry {
|
||||
client := &http.Client{Timeout: durationEnv(getenv, "ZHINIAN_PROVIDER_TIMEOUT_MS", 30*time.Second)}
|
||||
maximum := positiveInt64Env(getenv, "ZHINIAN_PROVIDER_MAX_RESPONSE_BYTES", 2<<20)
|
||||
return jobs.ProviderRegistry{
|
||||
"mock": providers.NewMock(firstNonEmpty(getenv("ZHINIAN_MOCK_SEED"), "zhinian")),
|
||||
"volcengine-visual": providers.NewVolcengine(providers.Config{
|
||||
BaseURL: firstNonEmpty(getenv("VOLCENGINE_VISUAL_ENDPOINT"), "https://visual.volcengineapi.com"),
|
||||
Model: imageModel(getenv), AccessKeyID: getenv("VOLCENGINE_ACCESS_KEY_ID"), SecretAccessKey: getenv("VOLCENGINE_SECRET_ACCESS_KEY"),
|
||||
@@ -180,18 +152,17 @@ func buildProviderRegistry(getenv postgres.Getenv) jobs.ProviderRegistry {
|
||||
}
|
||||
|
||||
func providerImageTargets(getenv postgres.Getenv) map[string]jobs.ProviderTarget {
|
||||
jimengConfigured := strings.TrimSpace(getenv("VOLCENGINE_ACCESS_KEY_ID")) != "" && strings.TrimSpace(getenv("VOLCENGINE_SECRET_ACCESS_KEY")) != ""
|
||||
return map[string]jobs.ProviderTarget{
|
||||
"jimeng": {
|
||||
Provider: providerOrMock("volcengine-visual", mockEnabled(getenv, "JIMENG_VISUAL_MOCK", jimengConfigured)),
|
||||
Provider: "volcengine-visual",
|
||||
Model: firstNonEmpty(getenv("JIMENG_IMAGE_GENERATE_46_REQ_KEY"), "jimeng_seedream46_cvtob"),
|
||||
},
|
||||
"evolink": {
|
||||
Provider: providerOrMock("evolink", mockEnabled(getenv, "EVOLINK_MOCK", strings.TrimSpace(getenv("EVOLINK_API_KEY")) != "")),
|
||||
Provider: "evolink",
|
||||
Model: firstNonEmpty(getenv("EVOLINK_IMAGE_MODEL"), "gpt-image-2"),
|
||||
},
|
||||
"bailian": {
|
||||
Provider: providerOrMock("bailian", mockEnabled(getenv, "BAILIAN_MOCK", bailianAPIKey(getenv) != "")),
|
||||
Provider: "bailian",
|
||||
Model: firstNonEmpty(getenv("BAILIAN_IMAGE_MODEL"), "wan2.7-image-pro"),
|
||||
},
|
||||
}
|
||||
@@ -200,7 +171,7 @@ func providerImageTargets(getenv postgres.Getenv) map[string]jobs.ProviderTarget
|
||||
func providerVideoTargets(getenv postgres.Getenv) map[string]jobs.ProviderTarget {
|
||||
return map[string]jobs.ProviderTarget{
|
||||
"seedance": {
|
||||
Provider: providerOrMock("seedance", mockEnabled(getenv, "SEEDANCE_MOCK", strings.TrimSpace(getenv("SEEDANCE_API_KEY")) != "")),
|
||||
Provider: "seedance",
|
||||
Model: firstNonEmpty(getenv("SEEDANCE_MODEL"), "doubao-seedance-2-0-260128"),
|
||||
Settings: map[string]any{
|
||||
"ratio": firstNonEmpty(getenv("SEEDANCE_DEFAULT_RATIO"), "9:16"),
|
||||
@@ -209,17 +180,30 @@ func providerVideoTargets(getenv postgres.Getenv) map[string]jobs.ProviderTarget
|
||||
},
|
||||
},
|
||||
"bailian": {
|
||||
Provider: providerOrMock("bailian", mockEnabled(getenv, "BAILIAN_MOCK", bailianAPIKey(getenv) != "")),
|
||||
Provider: "bailian",
|
||||
Model: firstNonEmpty(getenv("BAILIAN_VIDEO_MODEL"), "wan2.7-i2v-2026-04-25"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func providerOrMock(provider string, mocked bool) string {
|
||||
if mocked {
|
||||
return "mock"
|
||||
func validateProductionProviderConfiguration(getenv postgres.Getenv) error {
|
||||
missing := make([]string, 0, 4)
|
||||
if strings.TrimSpace(getenv("VOLCENGINE_ACCESS_KEY_ID")) == "" || strings.TrimSpace(getenv("VOLCENGINE_SECRET_ACCESS_KEY")) == "" {
|
||||
missing = append(missing, "即梦 VOLCENGINE_ACCESS_KEY_ID/VOLCENGINE_SECRET_ACCESS_KEY")
|
||||
}
|
||||
return provider
|
||||
if strings.TrimSpace(getenv("EVOLINK_API_KEY")) == "" {
|
||||
missing = append(missing, "EvoLink EVOLINK_API_KEY")
|
||||
}
|
||||
if bailianAPIKey(getenv) == "" {
|
||||
missing = append(missing, "百炼 BAILIAN_API_KEY/DASHSCOPE_API_KEY")
|
||||
}
|
||||
if strings.TrimSpace(getenv("SEEDANCE_API_KEY")) == "" {
|
||||
missing = append(missing, "Seedance SEEDANCE_API_KEY")
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("生产环境缺少真实服务商凭据:%s", strings.Join(missing, ";"))
|
||||
}
|
||||
|
||||
func defaultWebhookSender(getenv postgres.Getenv) (*webhook.HTTPSender, error) {
|
||||
@@ -267,12 +251,6 @@ func runtimeHealthDetails(getenv postgres.Getenv) httpapi.HealthDetails {
|
||||
visualConfigured := strings.TrimSpace(getenv("VOLCENGINE_ACCESS_KEY_ID")) != "" && strings.TrimSpace(getenv("VOLCENGINE_SECRET_ACCESS_KEY")) != ""
|
||||
auth, _ := ParseAuthConfig(getenv)
|
||||
bailianKey := bailianAPIKey(getenv)
|
||||
bailianMode := "missing"
|
||||
if mockFlagEnabled(getenv("BAILIAN_MOCK")) {
|
||||
bailianMode = "mock"
|
||||
} else if bailianKey != "" {
|
||||
bailianMode = "bailian"
|
||||
}
|
||||
authMode := "disabled"
|
||||
if auth.Required {
|
||||
authMode = "missing"
|
||||
@@ -281,10 +259,10 @@ func runtimeHealthDetails(getenv postgres.Getenv) httpapi.HealthDetails {
|
||||
}
|
||||
}
|
||||
return httpapi.HealthDetails{
|
||||
VisualAPIMode: providerMode(mockEnabled(getenv, "JIMENG_VISUAL_MOCK", visualConfigured), "volcengine"),
|
||||
EvolinkMode: providerMode(mockEnabled(getenv, "EVOLINK_MOCK", strings.TrimSpace(getenv("EVOLINK_API_KEY")) != ""), "evolink"),
|
||||
SeedanceMode: providerMode(mockEnabled(getenv, "SEEDANCE_MOCK", strings.TrimSpace(getenv("SEEDANCE_API_KEY")) != ""), "seedance"),
|
||||
BailianMode: bailianMode,
|
||||
VisualAPIMode: providerMode(visualConfigured, "volcengine"),
|
||||
EvolinkMode: providerMode(strings.TrimSpace(getenv("EVOLINK_API_KEY")) != "", "evolink"),
|
||||
SeedanceMode: providerMode(strings.TrimSpace(getenv("SEEDANCE_API_KEY")) != "", "seedance"),
|
||||
BailianMode: providerMode(bailianKey != "", "bailian"),
|
||||
AuthMode: authMode,
|
||||
Capabilities: []any{
|
||||
map[string]any{"id": "image.generate", "label": "图片生成 4.6", "engine": image, "engineLabel": imageLabel, "reqKey": imageModel(getenv)},
|
||||
@@ -293,22 +271,13 @@ func runtimeHealthDetails(getenv postgres.Getenv) httpapi.HealthDetails {
|
||||
}
|
||||
}
|
||||
|
||||
func providerMode(mock bool, live string) string {
|
||||
if mock {
|
||||
return "mock"
|
||||
func providerMode(configured bool, live string) string {
|
||||
if !configured {
|
||||
return "missing"
|
||||
}
|
||||
return live
|
||||
}
|
||||
|
||||
func mockFlagEnabled(value string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "1", "true":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func remoteAssetMaxBytes(getenv postgres.Getenv) int64 {
|
||||
return positiveInt64Env(getenv, "ZHINIAN_REMOTE_ASSET_MAX_BYTES", 20<<20)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/assets"
|
||||
@@ -81,7 +82,7 @@ func TestRuntimeHealthDetailsMatchTypeScriptDefaultsAndConfiguredModes(t *testin
|
||||
"ZHINIAN_AUTH_SESSION_SECRET": "session-secret",
|
||||
}
|
||||
details := runtimeHealthDetails(func(name string) string { return values[name] })
|
||||
if details.VisualAPIMode != "volcengine" || details.EvolinkMode != "mock" || details.SeedanceMode != "seedance" || details.BailianMode != "missing" || details.AuthMode != "configured" {
|
||||
if details.VisualAPIMode != "volcengine" || details.EvolinkMode != "missing" || details.SeedanceMode != "seedance" || details.BailianMode != "missing" || details.AuthMode != "configured" {
|
||||
t.Fatalf("details = %+v", details)
|
||||
}
|
||||
if len(details.Capabilities) != 2 {
|
||||
@@ -94,17 +95,13 @@ func TestRuntimeHealthDetailsMatchTypeScriptDefaultsAndConfiguredModes(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeHealthDetailsHonorExplicitMockFlagsAndImageEngine(t *testing.T) {
|
||||
func TestRuntimeHealthDetailsReportConfiguredProvidersAndImageEngine(t *testing.T) {
|
||||
values := map[string]string{
|
||||
"IMAGE_GENERATE_ENGINE": "bailian",
|
||||
"BAILIAN_API_KEY": "bailian-key",
|
||||
"BAILIAN_MOCK": "true",
|
||||
"JIMENG_VISUAL_MOCK": "true",
|
||||
"EVOLINK_MOCK": "false",
|
||||
"SEEDANCE_MOCK": "true",
|
||||
}
|
||||
details := runtimeHealthDetails(func(name string) string { return values[name] })
|
||||
if details.VisualAPIMode != "mock" || details.EvolinkMode != "evolink" || details.SeedanceMode != "mock" || details.BailianMode != "mock" || details.AuthMode != "disabled" {
|
||||
if details.VisualAPIMode != "missing" || details.EvolinkMode != "missing" || details.SeedanceMode != "missing" || details.BailianMode != "bailian" || details.AuthMode != "disabled" {
|
||||
t.Fatalf("details = %+v", details)
|
||||
}
|
||||
image := details.Capabilities[0].(map[string]any)
|
||||
@@ -113,6 +110,38 @@ func TestRuntimeHealthDetailsHonorExplicitMockFlagsAndImageEngine(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateProductionProviderConfigurationRequiresAllRealCredentials(t *testing.T) {
|
||||
if err := validateProductionProviderConfiguration(func(string) string { return "" }); err == nil {
|
||||
t.Fatal("validateProductionProviderConfiguration() unexpectedly succeeded")
|
||||
} else if !strings.Contains(err.Error(), "EVOLINK_API_KEY") || !strings.Contains(err.Error(), "SEEDANCE_API_KEY") {
|
||||
t.Fatalf("error = %v", err)
|
||||
}
|
||||
values := map[string]string{
|
||||
"VOLCENGINE_ACCESS_KEY_ID": "access",
|
||||
"VOLCENGINE_SECRET_ACCESS_KEY": "secret",
|
||||
"EVOLINK_API_KEY": "evolink",
|
||||
"BAILIAN_API_KEY": "bailian",
|
||||
"SEEDANCE_API_KEY": "seedance",
|
||||
}
|
||||
if err := validateProductionProviderConfiguration(func(name string) string { return values[name] }); err != nil {
|
||||
t.Fatalf("validateProductionProviderConfiguration() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderTargetsNeverSelectRemovedProvider(t *testing.T) {
|
||||
getenv := func(string) string { return "" }
|
||||
for engine, target := range providerImageTargets(getenv) {
|
||||
if target.Provider == "mock" {
|
||||
t.Fatalf("image engine %s selected removed provider", engine)
|
||||
}
|
||||
}
|
||||
for engine, target := range providerVideoTargets(getenv) {
|
||||
if target.Provider == "mock" {
|
||||
t.Fatalf("video engine %s selected removed provider", engine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapabilitySummaryMatchesConfiguredDefaultVideoEngine(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name, engine, wantEngine, wantProvider, wantModel string
|
||||
|
||||
Reference in new issue
Block a user