Files
seanime-docker/seanime-2.9.10/internal/plugin/system_test.go
2025-09-20 14:08:38 +01:00

413 lines
9.8 KiB
Go

package plugin
import (
"seanime/internal/extension"
"testing"
"github.com/samber/mo"
"github.com/stretchr/testify/assert"
)
// Mock AppContextImpl for testing
type mockAppContext struct {
AppContextImpl
mockPaths map[string][]string
}
// Create a new mock context with initialized fields
func newMockAppContext(paths map[string][]string) *mockAppContext {
ctx := &mockAppContext{
mockPaths: paths,
}
// Initialize the animeLibraryPaths field with mock data
if libraryPaths, ok := paths["SEANIME_ANIME_LIBRARY"]; ok {
ctx.animeLibraryPaths = mo.Some(libraryPaths)
} else {
ctx.animeLibraryPaths = mo.Some([]string{})
}
return ctx
}
func TestIsAllowedPath(t *testing.T) {
// Create mock context with predefined paths
mockCtx := newMockAppContext(map[string][]string{
"SEANIME_ANIME_LIBRARY": {"/anime/lib1", "/anime/lib2"},
"HOME": {"/home/user"},
"TEMP": {"/tmp"},
})
tests := []struct {
name string
ext *extension.Extension
path string
mode int
expected bool
}{
{
name: "nil extension",
ext: nil,
path: "/some/path",
mode: AllowPathRead,
expected: false,
},
{
name: "no patterns",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
},
},
},
path: "/some/path",
mode: AllowPathRead,
expected: false,
},
{
name: "simple path match",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{"/test/*.txt"},
},
},
},
},
path: "/test/file.txt",
mode: AllowPathRead,
expected: true,
},
{
name: "multiple library paths - first match",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
},
},
},
},
path: "/anime/lib1/file.txt",
mode: AllowPathRead,
expected: true,
},
{
name: "multiple library paths - second match",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
},
},
},
},
path: "/anime/lib2/file.txt",
mode: AllowPathRead,
expected: true,
},
{
name: "write mode with read pattern",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
},
},
},
path: "/test/file.txt",
mode: AllowPathWrite,
expected: false,
},
{
name: "multiple patterns - match one",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
},
},
},
},
path: "/anime/lib1/file.txt",
mode: AllowPathRead,
expected: true,
},
{
name: "no matching pattern",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
},
},
},
},
path: "/anime/lib1/file.txt",
mode: AllowPathRead,
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := mockCtx.isAllowedPath(tt.ext, tt.path, tt.mode)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsAllowedCommand(t *testing.T) {
// Create mock context
mockCtx := newMockAppContext(map[string][]string{
"HOME": {"/home/user"},
"SEANIME_ANIME_LIBRARY": {}, // Empty but initialized
})
tests := []struct {
name string
ext *extension.Extension
cmd string
args []string
expected bool
}{
{
name: "nil extension",
ext: nil,
cmd: "ls",
args: []string{"-l"},
expected: false,
},
{
name: "simple command no args",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
CommandScopes: []extension.CommandScope{
{
Command: "ls",
},
},
},
},
},
},
cmd: "ls",
args: []string{},
expected: true,
},
{
name: "command with fixed args - match",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
CommandScopes: []extension.CommandScope{
{
Command: "git",
Args: []extension.CommandArg{
{Value: "pull"},
{Value: "origin"},
{Value: "main"},
},
},
},
},
},
},
},
cmd: "git",
args: []string{"pull", "origin", "main"},
expected: true,
},
{
name: "command with fixed args - no match",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
CommandScopes: []extension.CommandScope{
{
Command: "git",
Args: []extension.CommandArg{
{Value: "pull"},
},
},
},
},
},
},
},
cmd: "git",
args: []string{"push"},
expected: false,
},
{
name: "command with $ARGS validator",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
CommandScopes: []extension.CommandScope{
{
Command: "echo",
Args: []extension.CommandArg{
{Validator: "$ARGS"},
},
},
},
},
},
},
},
cmd: "echo",
args: []string{"hello", "world"},
expected: true,
},
{
name: "command with regex validator - match",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
CommandScopes: []extension.CommandScope{
{
Command: "open",
Args: []extension.CommandArg{
{Validator: "^https?://.*$"},
},
},
},
},
},
},
},
cmd: "open",
args: []string{"https://example.com"},
expected: true,
},
{
name: "command with regex validator - no match",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
CommandScopes: []extension.CommandScope{
{
Command: "open",
Args: []extension.CommandArg{
{Validator: "^https?://.*$"},
},
},
},
},
},
},
},
cmd: "open",
args: []string{"file://example.com"},
expected: false,
},
{
name: "command with $PATH validator",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
CommandScopes: []extension.CommandScope{
{
Command: "open",
Args: []extension.CommandArg{
{Validator: "$PATH"},
},
},
},
WritePaths: []string{"$SEANIME_ANIME_LIBRARY/**"},
},
},
},
},
cmd: "open",
args: []string{"/anime/lib1/test.txt"},
expected: false, // Directory does not exist on the machine
},
{
name: "too many args",
ext: &extension.Extension{
Plugin: &extension.PluginManifest{
Permissions: extension.PluginPermissions{
Scopes: []extension.PluginPermissionScope{
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
CommandScopes: []extension.CommandScope{
{
Command: "ls",
Args: []extension.CommandArg{
{Value: "-l"},
},
},
},
},
},
},
},
cmd: "ls",
args: []string{"-l", "-a"},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := mockCtx.isAllowedCommand(tt.ext, tt.cmd, tt.args...)
assert.Equal(t, tt.expected, result)
})
}
}