- Read library ID from Target.Folder.ID everywhere - Guard scan queue enqueue against nil Folder - Simplify admin API key auth error plumbing
211 lines
5.7 KiB
Go
211 lines
5.7 KiB
Go
package scantrigger
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/Silo-Server/silo-server/internal/catalog"
|
|
"github.com/Silo-Server/silo-server/internal/models"
|
|
)
|
|
|
|
type fakeFolderRepo struct {
|
|
folders []*models.MediaFolder
|
|
listCalls int
|
|
}
|
|
|
|
func (r *fakeFolderRepo) GetByID(_ context.Context, id int) (*models.MediaFolder, error) {
|
|
for _, folder := range r.folders {
|
|
if folder.ID == id {
|
|
return folder, nil
|
|
}
|
|
}
|
|
return nil, catalog.ErrFolderNotFound
|
|
}
|
|
|
|
func (r *fakeFolderRepo) List(context.Context) ([]*models.MediaFolder, error) {
|
|
r.listCalls++
|
|
return r.folders, nil
|
|
}
|
|
|
|
func TestResolverClassifiesLibraryRoot(t *testing.T) {
|
|
root := t.TempDir()
|
|
repo := &fakeFolderRepo{folders: []*models.MediaFolder{{
|
|
ID: 7,
|
|
Name: "Movies",
|
|
Enabled: true,
|
|
Paths: []string{root},
|
|
}}}
|
|
|
|
target, err := NewResolver(repo).Resolve(context.Background(), Request{Path: root})
|
|
if err != nil {
|
|
t.Fatalf("Resolve returned error: %v", err)
|
|
}
|
|
if target.Folder == nil || target.Folder.ID != 7 || target.Mode != ModeLibrary || target.Path != "" {
|
|
t.Fatalf("unexpected target: %#v", target)
|
|
}
|
|
}
|
|
|
|
func TestResolverClassifiesSubtree(t *testing.T) {
|
|
root := t.TempDir()
|
|
subtree := filepath.Join(root, "Show")
|
|
if err := os.Mkdir(subtree, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo := &fakeFolderRepo{folders: []*models.MediaFolder{{
|
|
ID: 8,
|
|
Name: "TV",
|
|
Enabled: true,
|
|
Paths: []string{root},
|
|
}}}
|
|
|
|
target, err := NewResolver(repo).Resolve(context.Background(), Request{Path: subtree})
|
|
if err != nil {
|
|
t.Fatalf("Resolve returned error: %v", err)
|
|
}
|
|
if target.Folder == nil || target.Folder.ID != 8 || target.Mode != ModeSubtree || target.Path != filepath.Clean(subtree) {
|
|
t.Fatalf("unexpected target: %#v", target)
|
|
}
|
|
}
|
|
|
|
func TestResolverClassifiesVideoFile(t *testing.T) {
|
|
root := t.TempDir()
|
|
filePath := filepath.Join(root, "Movie (2024).mkv")
|
|
if err := os.WriteFile(filePath, []byte("test"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo := &fakeFolderRepo{folders: []*models.MediaFolder{{
|
|
ID: 9,
|
|
Name: "Movies",
|
|
Enabled: true,
|
|
Paths: []string{root},
|
|
}}}
|
|
|
|
target, err := NewResolver(repo).Resolve(context.Background(), Request{Path: filePath})
|
|
if err != nil {
|
|
t.Fatalf("Resolve returned error: %v", err)
|
|
}
|
|
if target.Folder == nil || target.Folder.ID != 9 || target.Mode != ModeFile || target.Path != filepath.Clean(filePath) {
|
|
t.Fatalf("unexpected target: %#v", target)
|
|
}
|
|
}
|
|
|
|
func TestResolverRejectsDisabledLibrary(t *testing.T) {
|
|
root := t.TempDir()
|
|
repo := &fakeFolderRepo{folders: []*models.MediaFolder{{
|
|
ID: 10,
|
|
Name: "Disabled",
|
|
Enabled: false,
|
|
Paths: []string{root},
|
|
}}}
|
|
|
|
_, err := NewResolver(repo).Resolve(context.Background(), Request{Path: root})
|
|
var reqErr *RequestError
|
|
if !errors.As(err, &reqErr) {
|
|
t.Fatalf("expected RequestError, got %T: %v", err, err)
|
|
}
|
|
if reqErr.Status != http.StatusConflict || reqErr.Code != "conflict" {
|
|
t.Fatalf("unexpected error: %#v", reqErr)
|
|
}
|
|
}
|
|
|
|
func TestResolveAllIsAllOrFail(t *testing.T) {
|
|
root := t.TempDir()
|
|
valid := filepath.Join(root, "Movie.mkv")
|
|
if err := os.WriteFile(valid, []byte("test"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
repo := &fakeFolderRepo{folders: []*models.MediaFolder{{
|
|
ID: 11,
|
|
Name: "Movies",
|
|
Enabled: true,
|
|
Paths: []string{root},
|
|
}}}
|
|
|
|
_, err := NewResolver(repo).ResolveAll(context.Background(), []Request{
|
|
{Path: valid},
|
|
{Path: filepath.Join(root, "missing.mkv")},
|
|
})
|
|
var reqErr *RequestError
|
|
if !errors.As(err, &reqErr) {
|
|
t.Fatalf("expected RequestError, got %T: %v", err, err)
|
|
}
|
|
if reqErr.Message != "Path does not exist" {
|
|
t.Fatalf("unexpected error message: %q", reqErr.Message)
|
|
}
|
|
}
|
|
|
|
func TestResolveAllReusesPathOnlyLibraryList(t *testing.T) {
|
|
root := t.TempDir()
|
|
first := filepath.Join(root, "First.mkv")
|
|
second := filepath.Join(root, "Second.mkv")
|
|
for _, path := range []string{first, second} {
|
|
if err := os.WriteFile(path, []byte("test"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
repo := &fakeFolderRepo{folders: []*models.MediaFolder{{
|
|
ID: 12,
|
|
Name: "Movies",
|
|
Enabled: true,
|
|
Paths: []string{root},
|
|
}}}
|
|
|
|
targets, err := NewResolver(repo).ResolveAll(context.Background(), []Request{
|
|
{Path: first},
|
|
{Path: second},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ResolveAll returned error: %v", err)
|
|
}
|
|
if len(targets) != 2 {
|
|
t.Fatalf("expected two targets, got %d", len(targets))
|
|
}
|
|
if repo.listCalls != 1 {
|
|
t.Fatalf("expected one folder list lookup, got %d", repo.listCalls)
|
|
}
|
|
}
|
|
|
|
type fakeQueue struct {
|
|
calls []Target
|
|
batches [][]Target
|
|
batchErr error
|
|
}
|
|
|
|
func (q *fakeQueue) EnqueueScan(_ context.Context, folderID int, mode, path, trigger string) (bool, error) {
|
|
q.calls = append(q.calls, Target{Folder: &models.MediaFolder{ID: folderID}, Mode: mode, Path: path, Trigger: trigger})
|
|
return true, nil
|
|
}
|
|
|
|
func (q *fakeQueue) EnqueueScans(_ context.Context, targets []Target) error {
|
|
copied := append([]Target(nil), targets...)
|
|
q.batches = append(q.batches, copied)
|
|
if q.batchErr != nil {
|
|
return q.batchErr
|
|
}
|
|
q.calls = append(q.calls, targets...)
|
|
return nil
|
|
}
|
|
|
|
func TestEnqueueAllUsesBatchQueue(t *testing.T) {
|
|
queue := &fakeQueue{}
|
|
folder := &models.MediaFolder{ID: 1}
|
|
targets := []Target{
|
|
{Folder: folder, Mode: ModeFile, Path: "/media/one.mkv", Trigger: "autoscan"},
|
|
{Folder: folder, Mode: ModeFile, Path: "/media/two.mkv", Trigger: "autoscan"},
|
|
}
|
|
|
|
if err := EnqueueAll(context.Background(), queue, targets); err != nil {
|
|
t.Fatalf("EnqueueAll returned error: %v", err)
|
|
}
|
|
if len(queue.batches) != 1 {
|
|
t.Fatalf("expected one batch enqueue, got %d", len(queue.batches))
|
|
}
|
|
if len(queue.calls) != 2 {
|
|
t.Fatalf("expected two queued calls from batch, got %d", len(queue.calls))
|
|
}
|
|
}
|