feat(cli): upgrade command, rich status, and version cache
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s

- 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:
Deivid Soto 2026-03-31 22:05:43 +02:00
parent 01d62ffa13
commit 3e0f3a5a64
33 changed files with 7084 additions and 65 deletions

View file

@ -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