Files
LocalAI/core/services/backends_test.go
Ettore Di Giacinto e1874cdb54 feat(ui): add mask to install custom backends (#7559)
* feat: allow to install backends from URL in the WebUI and API

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* tests

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* trace backends installations

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2025-12-13 19:11:32 +01:00

194 lines
5.1 KiB
Go

package services_test
import (
"context"
"os"
"path/filepath"
"github.com/mudler/LocalAI/core/config"
"github.com/mudler/LocalAI/core/services"
"github.com/mudler/LocalAI/pkg/model"
"github.com/mudler/LocalAI/pkg/system"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v2"
)
var _ = Describe("InstallExternalBackend", func() {
var (
tempDir string
galleries []config.Gallery
ml *model.ModelLoader
systemState *system.SystemState
)
BeforeEach(func() {
var err error
tempDir, err = os.MkdirTemp("", "backends-service-test-*")
Expect(err).NotTo(HaveOccurred())
systemState, err = system.GetSystemState(system.WithBackendPath(tempDir))
Expect(err).NotTo(HaveOccurred())
ml = model.NewModelLoader(systemState)
// Setup test gallery
galleries = []config.Gallery{
{
Name: "test-gallery",
URL: "file://" + filepath.Join(tempDir, "test-gallery.yaml"),
},
}
})
AfterEach(func() {
os.RemoveAll(tempDir)
})
Context("with gallery backend name", func() {
BeforeEach(func() {
// Create a test gallery file with a test backend
testBackend := []map[string]interface{}{
{
"name": "test-backend",
"uri": "https://gist.githubusercontent.com/mudler/71d5376bc2aa168873fa519fa9f4bd56/raw/testbackend/run.sh",
},
}
data, err := yaml.Marshal(testBackend)
Expect(err).NotTo(HaveOccurred())
err = os.WriteFile(filepath.Join(tempDir, "test-gallery.yaml"), data, 0644)
Expect(err).NotTo(HaveOccurred())
})
It("should fail when name or alias is provided for gallery backend", func() {
err := services.InstallExternalBackend(
context.Background(),
galleries,
systemState,
ml,
nil,
"test-backend", // gallery name
"custom-name", // name should not be allowed
"",
)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("specifying a name or alias is not supported for gallery backends"))
})
It("should fail when backend is not found in gallery", func() {
err := services.InstallExternalBackend(
context.Background(),
galleries,
systemState,
ml,
nil,
"non-existent-backend",
"",
"",
)
Expect(err).To(HaveOccurred())
})
})
Context("with OCI image", func() {
It("should fail when name is not provided for OCI image", func() {
err := services.InstallExternalBackend(
context.Background(),
galleries,
systemState,
ml,
nil,
"oci://quay.io/mudler/tests:localai-backend-test",
"", // name is required for OCI images
"",
)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("specifying a name is required for OCI images"))
})
})
Context("with directory path", func() {
var testBackendPath string
BeforeEach(func() {
// Create a test backend directory with required files
testBackendPath = filepath.Join(tempDir, "source-backend")
err := os.MkdirAll(testBackendPath, 0750)
Expect(err).NotTo(HaveOccurred())
// Create run.sh
err = os.WriteFile(filepath.Join(testBackendPath, "run.sh"), []byte("#!/bin/bash\necho test"), 0755)
Expect(err).NotTo(HaveOccurred())
})
It("should infer name from directory path when name is not provided", func() {
// This test verifies that the function attempts to install using the directory name
// The actual installation may fail due to test environment limitations
err := services.InstallExternalBackend(
context.Background(),
galleries,
systemState,
ml,
nil,
testBackendPath,
"", // name should be inferred as "source-backend"
"",
)
// The function should at least attempt to install with the inferred name
// Even if it fails for other reasons, it shouldn't fail due to missing name
if err != nil {
Expect(err.Error()).NotTo(ContainSubstring("name is required"))
}
})
It("should use provided name when specified", func() {
err := services.InstallExternalBackend(
context.Background(),
galleries,
systemState,
ml,
nil,
testBackendPath,
"custom-backend-name",
"",
)
// The function should use the provided name
if err != nil {
Expect(err.Error()).NotTo(ContainSubstring("name is required"))
}
})
It("should support alias when provided", func() {
err := services.InstallExternalBackend(
context.Background(),
galleries,
systemState,
ml,
nil,
testBackendPath,
"custom-backend-name",
"custom-alias",
)
// The function should accept alias for directory paths
if err != nil {
Expect(err.Error()).NotTo(ContainSubstring("alias is not supported"))
}
})
})
})
var _ = Describe("GalleryOp with External Backend", func() {
It("should have external backend fields in GalleryOp", func() {
// Test that the GalleryOp struct has the new external backend fields
op := services.GalleryOp[string, string]{
ExternalURI: "oci://example.com/backend:latest",
ExternalName: "test-backend",
ExternalAlias: "test-alias",
}
Expect(op.ExternalURI).To(Equal("oci://example.com/backend:latest"))
Expect(op.ExternalName).To(Equal("test-backend"))
Expect(op.ExternalAlias).To(Equal("test-alias"))
})
})