Files
LocalAI/core/backend/upscale_test.go
T
mudler-agentandEttore Di Giacinto 0177e4988c feat(images): add ESRGAN upscaling and Studio workflow (#12620)
* feat(images): add upscale model capabilities

* fix(config): keep SD GGML upscale opt-in

* feat(stablediffusion): add lazy ESRGAN upscale bridge

Keep native image ownership in C++ and serialize context creation, inference and teardown. Use upscale_scale in backend options as the explicit upscale-only marker because ModelOptions does not carry usecases.

Assisted-by: Codex:gpt-5

* fix(stablediffusion): validate ESRGAN settings

* fix(upscale): forward typed settings and contain resolved model paths

Typed settings replace legacy options without duplicates. Resolve model and trusted root symlinks before caching the model path, rejecting missing files and escapes.

Assisted-by: Codex:gpt-5

* fix(upscale): validate scale and clean failed HTTP outputs

Require an explicit scale bounded to 1-16, check backend results, and keep destination cleanup owned by the allocating endpoint. Cover validation, result failures, cleanup, and successful URL responses.

Assisted-by: Codex:gpt-5

* feat(studio): add inline image upscale workflow

Assisted-by: OpenAI:gpt-5

* test(studio): serve upscale capability fixtures

* fix(stablediffusion): default ESRGAN tiling to full frame

* feat(gallery): add RealESRGAN anime upscaler

* fix(stablediffusion): allow full-frame ESRGAN tiles

* fix(routes): select explicit upscale models

Give image upscaling dedicated usecase-filtered middleware and keep ordinary image models out of the endpoint. Mark and verify the legacy Diffusers x4 gallery model as an explicit upscaler.

Assisted-by: OpenAI:gpt-5 [shell]

* refactor(upscale): keep settings in backend options

* test(config): keep upscale options backend-specific

* fix: keep upscale routing metadata out of backend options

---------

Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
2026-10-11 00:30:09 +02:00

77 lines
2.5 KiB
Go

// SPDX-License-Identifier: MIT
package backend_test
import (
"context"
"errors"
"strings"
"testing"
"github.com/mudler/LocalAI/core/backend"
"github.com/mudler/LocalAI/core/config"
grpcPkg "github.com/mudler/LocalAI/pkg/grpc"
pb "github.com/mudler/LocalAI/pkg/grpc/proto"
"github.com/mudler/LocalAI/pkg/model"
"github.com/mudler/LocalAI/pkg/system"
ggrpc "google.golang.org/grpc"
)
type upscaleBackend struct {
grpcPkg.Backend
result *pb.Result
err error
request *pb.UpscaleImageRequest
}
func (*upscaleBackend) HealthCheck(context.Context) (bool, error) { return true, nil }
func (*upscaleBackend) IsBusy() bool { return false }
func (b *upscaleBackend) UpscaleImage(_ context.Context, in *pb.UpscaleImageRequest, _ ...ggrpc.CallOption) (*pb.Result, error) {
b.request = in
return b.result, b.err
}
func TestImageUpscaleResult(t *testing.T) {
transportErr := errors.New("transport failed")
for _, tc := range []struct {
name string
result *pb.Result
err error
want string
}{
{name: "nil", want: "nil"},
{name: "unsuccessful", result: &pb.Result{}, want: "failed"},
{name: "message", result: &pb.Result{Message: "native mismatch"}, want: "native mismatch"},
{name: "transport", err: transportErr, want: "transport failed"},
{name: "success", result: &pb.Result{Success: true}},
} {
t.Run(tc.name, func(t *testing.T) {
fake := &upscaleBackend{result: tc.result, err: tc.err}
loader := model.NewModelLoader(&system.SystemState{})
loader.SetModelRouter(func(_ context.Context, id string, _, _, _, _ string, _ *pb.ModelOptions, _ bool) (*model.Model, error) {
return model.NewModelWithClient(id, "test://upscale", fake), nil
})
cfg := config.ModelConfig{Name: "upscale", Backend: "stub"}
cfg.SetDefaults()
fn, err := backend.ImageUpscale(context.Background(), "source", "destination", 3, loader, cfg, config.NewApplicationConfig(config.WithSystemState(&system.SystemState{})))
if err != nil {
t.Fatal(err)
}
err = fn()
if tc.want == "" {
if err != nil {
t.Fatal(err)
}
} else if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("error = %v, want %q", err, tc.want)
}
if tc.err != nil && !errors.Is(err, tc.err) {
t.Fatalf("lost transport error: %v", err)
}
if fake.request == nil || fake.request.Scale != 3 || fake.request.Src != "source" || fake.request.Dst != "destination" {
t.Fatalf("unexpected request: %v", fake.request)
}
})
}
}