feat(cli): upgrade command, rich status, and version cache
- Replace `upgrade` stub with real command (alias for `self-update`) - Also register `update` as alias: `unarr update` works too - Rewrite `status` to show full config, disk usage, daemon state, and update availability with colored sections - Add version check cache (1h TTL) so `status` is instant on repeat runs - Guard against division by zero on empty filesystems - Guard against negative durations from clock skew - Guard against stale PID via heartbeat recency check (2 min) - Add comprehensive test coverage across agent, engine, upgrade, usenet, arr, library, mediaserver, and UI packages - Improve Makefile coverage target to exclude cmd/ glue code - Fix stream handler resource cleanup and ffprobe error handling
This commit is contained in:
parent
01d62ffa13
commit
3e0f3a5a64
33 changed files with 7084 additions and 65 deletions
|
|
@ -2,6 +2,8 @@ package mediaserver
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -69,6 +71,96 @@ func TestJellyfinParsing(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPlexTokenFromPrefs(t *testing.T) {
|
||||
t.Run("valid prefs", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
prefsPath := filepath.Join(dir, "Preferences.xml")
|
||||
xml := `<?xml version="1.0" encoding="utf-8"?>
|
||||
<Preferences PlexOnlineToken="my-secret-token" OldestPreviousVersion="1.0"/>`
|
||||
os.WriteFile(prefsPath, []byte(xml), 0o644)
|
||||
|
||||
token := plexTokenFromPrefs(prefsPath)
|
||||
if token != "my-secret-token" {
|
||||
t.Errorf("token = %q, want my-secret-token", token)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no token attr", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
prefsPath := filepath.Join(dir, "Preferences.xml")
|
||||
xml := `<?xml version="1.0"?><Preferences/>`
|
||||
os.WriteFile(prefsPath, []byte(xml), 0o644)
|
||||
|
||||
token := plexTokenFromPrefs(prefsPath)
|
||||
if token != "" {
|
||||
t.Errorf("token = %q, want empty", token)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("file not found", func(t *testing.T) {
|
||||
token := plexTokenFromPrefs("/nonexistent/Preferences.xml")
|
||||
if token != "" {
|
||||
t.Errorf("token = %q, want empty", token)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid xml", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
prefsPath := filepath.Join(dir, "Preferences.xml")
|
||||
os.WriteFile(prefsPath, []byte("not xml at all"), 0o644)
|
||||
|
||||
token := plexTokenFromPrefs(prefsPath)
|
||||
if token != "" {
|
||||
t.Errorf("token = %q, want empty", token)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestParsePlexSectionsMultipleLocations(t *testing.T) {
|
||||
body := `{
|
||||
"MediaContainer": {
|
||||
"Directory": [
|
||||
{
|
||||
"title": "Movies",
|
||||
"Location": [
|
||||
{"path": "/media/movies"},
|
||||
{"path": "/media/movies2"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
paths := parsePlexSections([]byte(body))
|
||||
if len(paths) != 2 {
|
||||
t.Fatalf("expected 2 paths, got %d", len(paths))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePlexSectionsEmptyPath(t *testing.T) {
|
||||
body := `{
|
||||
"MediaContainer": {
|
||||
"Directory": [
|
||||
{
|
||||
"Location": [{"path": ""}, {"path": "/valid"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
paths := parsePlexSections([]byte(body))
|
||||
if len(paths) != 1 {
|
||||
t.Fatalf("expected 1 path (empty filtered), got %d: %v", len(paths), paths)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommonMediaDirs(t *testing.T) {
|
||||
dirs := commonMediaDirs()
|
||||
if len(dirs) == 0 {
|
||||
t.Error("expected at least some common media dirs")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParentDir(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue