forked from Plemya-x/ALR
		
	chore: refactor code
- remove legacy code - refactor search and add tests
This commit is contained in:
		@@ -306,7 +306,7 @@ func (b *Builder) executeFirstPass(
 | 
			
		||||
		interp.Env(expand.ListEnviron(env...)),                               // Устанавливаем окружение
 | 
			
		||||
		interp.StdIO(os.Stdin, os.Stdout, os.Stderr),                         // Устанавливаем стандартный ввод-вывод
 | 
			
		||||
		interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение
 | 
			
		||||
		interp.ReadDirHandler(handlers.RestrictedReadDir(scriptDir)),         // Ограничиваем чтение директорий
 | 
			
		||||
		interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)),        // Ограничиваем чтение директорий
 | 
			
		||||
		interp.StatHandler(handlers.RestrictedStat(scriptDir)),               // Ограничиваем доступ к статистике файлов
 | 
			
		||||
		interp.OpenHandler(handlers.RestrictedOpen(scriptDir)),               // Ограничиваем открытие файлов
 | 
			
		||||
	)
 | 
			
		||||
@@ -395,9 +395,11 @@ func (b *Builder) executeSecondPass(
 | 
			
		||||
 | 
			
		||||
	fakeroot := handlers.FakerootExecHandler(2 * time.Second) // Настраиваем "fakeroot" для выполнения
 | 
			
		||||
	runner, err := interp.New(
 | 
			
		||||
		interp.Env(expand.ListEnviron(env...)),                    // Устанавливаем окружение
 | 
			
		||||
		interp.StdIO(os.Stdin, os.Stdout, os.Stderr),              // Устанавливаем стандартный ввод-вывод
 | 
			
		||||
		interp.ExecHandler(helpers.Helpers.ExecHandler(fakeroot)), // Обрабатываем выполнение через fakeroot
 | 
			
		||||
		interp.Env(expand.ListEnviron(env...)),       // Устанавливаем окружение
 | 
			
		||||
		interp.StdIO(os.Stdin, os.Stdout, os.Stderr), // Устанавливаем стандартный ввод-вывод
 | 
			
		||||
		interp.ExecHandlers(func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
 | 
			
		||||
			return helpers.Helpers.ExecHandler(fakeroot)
 | 
			
		||||
		}), // Обрабатываем выполнение через fakeroot
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
 | 
			
		||||
	runner, err := interp.New(
 | 
			
		||||
		interp.OpenHandler(handlers.NopOpen),
 | 
			
		||||
		interp.ExecHandler(handlers.NopExec),
 | 
			
		||||
		interp.ReadDirHandler(handlers.NopReadDir),
 | 
			
		||||
		interp.ReadDirHandler2(handlers.NopReadDir),
 | 
			
		||||
		interp.StatHandler(handlers.NopStat),
 | 
			
		||||
		interp.Env(expand.ListEnviron()),
 | 
			
		||||
	)
 | 
			
		||||
 
 | 
			
		||||
@@ -77,7 +77,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		slog.Info(gotext.Get("Pulling repository"), "name", repo.Name)
 | 
			
		||||
		repoDir := filepath.Join(config.GetPaths(ctx).RepoDir, repo.Name)
 | 
			
		||||
		repoDir := filepath.Join(rs.cfg.GetPaths(ctx).RepoDir, repo.Name)
 | 
			
		||||
 | 
			
		||||
		var repoFS billy.Filesystem
 | 
			
		||||
		gitDir := filepath.Join(repoDir, ".git")
 | 
			
		||||
@@ -264,7 +264,7 @@ func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Ru
 | 
			
		||||
	return interp.New(
 | 
			
		||||
		interp.Env(expand.ListEnviron(env...)),
 | 
			
		||||
		interp.ExecHandler(handlers.NopExec),
 | 
			
		||||
		interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
 | 
			
		||||
		interp.ReadDirHandler2(handlers.RestrictedReadDir(repoDir)),
 | 
			
		||||
		interp.StatHandler(handlers.RestrictedStat(repoDir)),
 | 
			
		||||
		interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
 | 
			
		||||
		interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,60 +0,0 @@
 | 
			
		||||
// ALR - Any Linux Repository
 | 
			
		||||
// Copyright (C) 2025 Евгений Храмов
 | 
			
		||||
//
 | 
			
		||||
// This program is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// This program is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
package repos
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
 | 
			
		||||
	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
 | 
			
		||||
	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned
 | 
			
		||||
// and its packages will be written to the DB. If it does exist, it will be pulled.
 | 
			
		||||
// In this case, only changed packages will be processed if possible.
 | 
			
		||||
// If repos is set to nil, the repos in the ALR config will be used.
 | 
			
		||||
//
 | 
			
		||||
// Deprecated: use struct method
 | 
			
		||||
func Pull(ctx context.Context, repos []types.Repo) error {
 | 
			
		||||
	return GetInstance(ctx).Pull(ctx, repos)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// =======================
 | 
			
		||||
// FOR LEGACY ONLY
 | 
			
		||||
// =======================
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	reposInstance *Repos
 | 
			
		||||
	alrConfigOnce sync.Once
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Deprecated: For legacy only
 | 
			
		||||
func GetInstance(ctx context.Context) *Repos {
 | 
			
		||||
	alrConfigOnce.Do(func() {
 | 
			
		||||
		cfg := config.GetInstance(ctx)
 | 
			
		||||
		db := database.GetInstance(ctx)
 | 
			
		||||
 | 
			
		||||
		reposInstance = New(
 | 
			
		||||
			cfg,
 | 
			
		||||
			db,
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return reposInstance
 | 
			
		||||
}
 | 
			
		||||
@@ -21,166 +21,46 @@ package search
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
 | 
			
		||||
	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
 | 
			
		||||
	"github.com/jmoiron/sqlx"
 | 
			
		||||
 | 
			
		||||
	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Filter represents search filters.
 | 
			
		||||
type Filter int
 | 
			
		||||
 | 
			
		||||
// Filters
 | 
			
		||||
const (
 | 
			
		||||
	FilterNone Filter = iota
 | 
			
		||||
	FilterInRepo
 | 
			
		||||
	FilterSupportsArch
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SoryBy represents a value that packages can be sorted by.
 | 
			
		||||
type SortBy int
 | 
			
		||||
 | 
			
		||||
// Sort values
 | 
			
		||||
const (
 | 
			
		||||
	SortByNone = iota
 | 
			
		||||
	SortByName
 | 
			
		||||
	SortByRepo
 | 
			
		||||
	SortByVersion
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Package represents a package from ALR's database
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Name          string
 | 
			
		||||
	Version       string
 | 
			
		||||
	Release       int
 | 
			
		||||
	Epoch         uint
 | 
			
		||||
	Description   map[string]string
 | 
			
		||||
	Homepage      map[string]string
 | 
			
		||||
	Maintainer    map[string]string
 | 
			
		||||
	Architectures []string
 | 
			
		||||
	Licenses      []string
 | 
			
		||||
	Provides      []string
 | 
			
		||||
	Conflicts     []string
 | 
			
		||||
	Replaces      []string
 | 
			
		||||
	Depends       map[string][]string
 | 
			
		||||
	BuildDepends  map[string][]string
 | 
			
		||||
	OptDepends    map[string][]string
 | 
			
		||||
	Repository    string
 | 
			
		||||
type PackagesProvider interface {
 | 
			
		||||
	GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func convertPkg(p db.Package) Package {
 | 
			
		||||
	return Package{
 | 
			
		||||
		Name:          p.Name,
 | 
			
		||||
		Version:       p.Version,
 | 
			
		||||
		Release:       p.Release,
 | 
			
		||||
		Epoch:         p.Epoch,
 | 
			
		||||
		Description:   p.Description.Val,
 | 
			
		||||
		Homepage:      p.Homepage.Val,
 | 
			
		||||
		Maintainer:    p.Maintainer.Val,
 | 
			
		||||
		Architectures: p.Architectures.Val,
 | 
			
		||||
		Licenses:      p.Licenses.Val,
 | 
			
		||||
		Provides:      p.Provides.Val,
 | 
			
		||||
		Conflicts:     p.Conflicts.Val,
 | 
			
		||||
		Replaces:      p.Replaces.Val,
 | 
			
		||||
		Depends:       p.Depends.Val,
 | 
			
		||||
		BuildDepends:  p.BuildDepends.Val,
 | 
			
		||||
		OptDepends:    p.OptDepends.Val,
 | 
			
		||||
		Repository:    p.Repository,
 | 
			
		||||
type Searcher struct {
 | 
			
		||||
	pp PackagesProvider
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(pp PackagesProvider) *Searcher {
 | 
			
		||||
	return &Searcher{
 | 
			
		||||
		pp: pp,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Options contains the options for a search.
 | 
			
		||||
type Options struct {
 | 
			
		||||
	Filter      Filter
 | 
			
		||||
	FilterValue string
 | 
			
		||||
	SortBy      SortBy
 | 
			
		||||
	Limit       int64
 | 
			
		||||
	Query       string
 | 
			
		||||
}
 | 
			
		||||
func (s *Searcher) Search(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	opts *SearchOptions,
 | 
			
		||||
) ([]database.Package, error) {
 | 
			
		||||
	var packages []database.Package
 | 
			
		||||
 | 
			
		||||
// Search searches for packages in the database based on the given options.
 | 
			
		||||
func Search(ctx context.Context, opts Options) ([]Package, error) {
 | 
			
		||||
	query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
 | 
			
		||||
	args := []any{"%" + opts.Query + "%", "%" + opts.Query + "%", opts.Query}
 | 
			
		||||
 | 
			
		||||
	if opts.Filter != FilterNone {
 | 
			
		||||
		switch opts.Filter {
 | 
			
		||||
		case FilterInRepo:
 | 
			
		||||
			query += " AND repository = ?"
 | 
			
		||||
		case FilterSupportsArch:
 | 
			
		||||
			query += " AND json_array_contains(architectures, ?)"
 | 
			
		||||
		}
 | 
			
		||||
		args = append(args, opts.FilterValue)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.SortBy != SortByNone {
 | 
			
		||||
		switch opts.SortBy {
 | 
			
		||||
		case SortByName:
 | 
			
		||||
			query += " ORDER BY name"
 | 
			
		||||
		case SortByRepo:
 | 
			
		||||
			query += " ORDER BY repository"
 | 
			
		||||
		case SortByVersion:
 | 
			
		||||
			query += " ORDER BY version"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.Limit != 0 {
 | 
			
		||||
		query += " LIMIT " + strconv.FormatInt(opts.Limit, 10)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result, err := db.GetPkgs(ctx, query, args...)
 | 
			
		||||
	where, args := opts.WhereClause()
 | 
			
		||||
	result, err := s.pp.GetPkgs(ctx, where, args...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var out []Package
 | 
			
		||||
	for result.Next() {
 | 
			
		||||
		pkg := db.Package{}
 | 
			
		||||
		err = result.StructScan(&pkg)
 | 
			
		||||
		var dbPkg database.Package
 | 
			
		||||
		err = result.StructScan(&dbPkg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		out = append(out, convertPkg(pkg))
 | 
			
		||||
		packages = append(packages, dbPkg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return out, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPkg gets a single package from the database and returns it.
 | 
			
		||||
func GetPkg(ctx context.Context, repo, name string) (Package, error) {
 | 
			
		||||
	pkg, err := db.GetPkg(ctx, "name = ? AND repository = ?", name, repo)
 | 
			
		||||
	return convertPkg(*pkg), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// ErrInvalidArgument is an error returned by GetScript when one of its arguments
 | 
			
		||||
	// contain invalid characters
 | 
			
		||||
	ErrInvalidArgument = errors.New("name and repository must not contain . or /")
 | 
			
		||||
 | 
			
		||||
	// ErrScriptNotFound is returned by GetScript if it can't find the script requested
 | 
			
		||||
	// by the user.
 | 
			
		||||
	ErrScriptNotFound = errors.New("requested script not found")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetScript returns a reader containing the build script for a given package.
 | 
			
		||||
func GetScript(ctx context.Context, repo, name string) (io.ReadCloser, error) {
 | 
			
		||||
	if strings.Contains(name, "./") || strings.ContainsAny(repo, "./") {
 | 
			
		||||
		return nil, ErrInvalidArgument
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, repo, name, "alr.sh")
 | 
			
		||||
	fl, err := os.Open(scriptPath)
 | 
			
		||||
	if errors.Is(err, fs.ErrNotExist) {
 | 
			
		||||
		return nil, ErrScriptNotFound
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fl, nil
 | 
			
		||||
	return packages, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										86
									
								
								pkg/search/search_options_builder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								pkg/search/search_options_builder.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
// ALR - Any Linux Repository
 | 
			
		||||
// Copyright (C) 2025 Евгений Храмов
 | 
			
		||||
//
 | 
			
		||||
// This program is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// This program is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
package search
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SearchOptions struct {
 | 
			
		||||
	conditions []string
 | 
			
		||||
	args       []any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *SearchOptions) WhereClause() (string, []any) {
 | 
			
		||||
	if len(o.conditions) == 0 {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(o.conditions, " AND "), o.args
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SearchOptionsBuilder struct {
 | 
			
		||||
	options SearchOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSearchOptions() *SearchOptionsBuilder {
 | 
			
		||||
	return &SearchOptionsBuilder{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *SearchOptionsBuilder) withGeneralLike(key, value string) *SearchOptionsBuilder {
 | 
			
		||||
	if value != "" {
 | 
			
		||||
		b.options.conditions = append(b.options.conditions, fmt.Sprintf("%s LIKE ?", key))
 | 
			
		||||
		b.options.args = append(b.options.args, "%"+value+"%")
 | 
			
		||||
	}
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *SearchOptionsBuilder) withGeneralEqual(key string, value any) *SearchOptionsBuilder {
 | 
			
		||||
	if value != "" {
 | 
			
		||||
		b.options.conditions = append(b.options.conditions, fmt.Sprintf("%s = ?", key))
 | 
			
		||||
		b.options.args = append(b.options.args, value)
 | 
			
		||||
	}
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *SearchOptionsBuilder) withGeneralJsonArrayContains(key string, value any) *SearchOptionsBuilder {
 | 
			
		||||
	if value != "" {
 | 
			
		||||
		b.options.conditions = append(b.options.conditions, fmt.Sprintf("json_array_contains(%s, ?)", key))
 | 
			
		||||
		b.options.args = append(b.options.args, value)
 | 
			
		||||
	}
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *SearchOptionsBuilder) WithName(name string) *SearchOptionsBuilder {
 | 
			
		||||
	return b.withGeneralLike("name", name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *SearchOptionsBuilder) WithDescription(description string) *SearchOptionsBuilder {
 | 
			
		||||
	return b.withGeneralLike("description", description)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *SearchOptionsBuilder) WithRepository(repository string) *SearchOptionsBuilder {
 | 
			
		||||
	return b.withGeneralEqual("repository", repository)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *SearchOptionsBuilder) WithProvides(provides string) *SearchOptionsBuilder {
 | 
			
		||||
	return b.withGeneralJsonArrayContains("provides", provides)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b *SearchOptionsBuilder) Build() *SearchOptions {
 | 
			
		||||
	return &b.options
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								pkg/search/search_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								pkg/search/search_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
			
		||||
// ALR - Any Linux Repository
 | 
			
		||||
// Copyright (C) 2025 Евгений Храмов
 | 
			
		||||
//
 | 
			
		||||
// This program is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU General Public License as published by
 | 
			
		||||
// the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
// (at your option) any later version.
 | 
			
		||||
//
 | 
			
		||||
// This program is distributed in the hope that it will be useful,
 | 
			
		||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
// GNU General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
package search_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSearhOptionsBuilder(t *testing.T) {
 | 
			
		||||
	type testCase struct {
 | 
			
		||||
		name          string
 | 
			
		||||
		prepare       func() *search.SearchOptions
 | 
			
		||||
		expectedWhere string
 | 
			
		||||
		expectedArgs  []any
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tc := range []testCase{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Empty fields",
 | 
			
		||||
			prepare: func() *search.SearchOptions {
 | 
			
		||||
				return search.NewSearchOptions().
 | 
			
		||||
					Build()
 | 
			
		||||
			},
 | 
			
		||||
			expectedWhere: "",
 | 
			
		||||
			expectedArgs:  []any{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "All fields",
 | 
			
		||||
			prepare: func() *search.SearchOptions {
 | 
			
		||||
				return search.NewSearchOptions().
 | 
			
		||||
					WithName("foo").
 | 
			
		||||
					WithDescription("bar").
 | 
			
		||||
					WithRepository("buz").
 | 
			
		||||
					WithProvides("test").
 | 
			
		||||
					Build()
 | 
			
		||||
			},
 | 
			
		||||
			expectedWhere: "name LIKE ? AND description LIKE ? AND repository = ? AND json_array_contains(provides, ?)",
 | 
			
		||||
			expectedArgs:  []any{"%foo%", "%bar%", "buz", "test"},
 | 
			
		||||
		},
 | 
			
		||||
	} {
 | 
			
		||||
		t.Run(tc.name, func(t *testing.T) {
 | 
			
		||||
			whereClause, args := tc.prepare().WhereClause()
 | 
			
		||||
			assert.Equal(t, tc.expectedWhere, whereClause)
 | 
			
		||||
			assert.ElementsMatch(t, tc.expectedArgs, args)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user