feat: add SyncAll periodic sync pipeline and wire Navidrome client into App

This commit is contained in:
2026-07-19 22:22:46 +03:00
parent 40c4240693
commit dc4bdcdab0
9 changed files with 417 additions and 4 deletions

View File

@@ -12,6 +12,7 @@ import (
"naviwatcher/internal/config"
"naviwatcher/internal/database"
"naviwatcher/internal/musicbrainz"
"naviwatcher/internal/navidrome"
"naviwatcher/internal/scanner"
)
@@ -20,8 +21,14 @@ type App struct {
cfg *config.Config
db *database.DB
mbClient *musicbrainz.MusicBrainzClient
ndClient *navidrome.NavidromeClient
}
// navidromeClientFactory constructs the Navidrome client. It is a package-level
// variable (not a direct call to navidrome.NewClient) so tests can inject a stub
// without requiring a live Navidrome server for authentication.
var navidromeClientFactory = navidrome.NewClient
const defaultConfigPath = "config.yaml"
func main() {
@@ -76,10 +83,17 @@ func NewApp(ctx context.Context, cfg *config.Config, dbPath string) (*App, error
log.Printf("MusicBrainz client initialized (user-agent: %s)", cfg.MusicBrainz.UserAgent)
// Initialize Navidrome client (authenticates immediately; error if auth fails).
ndClient, err := navidromeClientFactory(cfg.Navidrome)
if err != nil {
return nil, fmt.Errorf("failed to initialize navidrome client: %w", err)
}
return &App{
cfg: cfg,
db: db,
mbClient: mbClient,
ndClient: ndClient,
}, nil
}
@@ -88,6 +102,10 @@ func (a *App) Close() {
if a.mbClient != nil {
a.mbClient.Close()
}
if a.ndClient != nil {
// NavidromeClient holds a stateless subsonic client; nothing to close
// beyond releasing idle connections tracked by the MusicBrainz client.
}
if a.db != nil {
if err := a.db.Close(); err != nil {
log.Printf("Error closing database: %v", err)

View File

@@ -12,6 +12,7 @@ import (
"naviwatcher/internal/config"
"naviwatcher/internal/database"
"naviwatcher/internal/navidrome"
)
func TestAppRun_ScanLogsMissingReleases(t *testing.T) {
@@ -135,6 +136,13 @@ func TestNewApp_CreatesMusicBrainzClient(t *testing.T) {
},
}
// Inject an unauthenticated Navidrome client so the test needs no live server.
prevFactory := navidromeClientFactory
navidromeClientFactory = func(c config.NavidromeConfig) (*navidrome.NavidromeClient, error) {
return navidrome.NewClientUnauthenticated(c), nil
}
defer func() { navidromeClientFactory = prevFactory }()
ctx := context.Background()
app, err := NewApp(ctx, cfg, ":memory:")
if err != nil {
@@ -145,6 +153,9 @@ func TestNewApp_CreatesMusicBrainzClient(t *testing.T) {
if app.mbClient == nil {
t.Fatal("expected MusicBrainz client to be initialized, got nil")
}
if app.ndClient == nil {
t.Fatal("expected Navidrome client to be initialized, got nil")
}
if app.db == nil {
t.Fatal("expected database to be initialized, got nil")
}
@@ -170,6 +181,12 @@ func TestNewApp_GracefulShutdown(t *testing.T) {
},
}
prevFactory := navidromeClientFactory
navidromeClientFactory = func(c config.NavidromeConfig) (*navidrome.NavidromeClient, error) {
return navidrome.NewClientUnauthenticated(c), nil
}
defer func() { navidromeClientFactory = prevFactory }()
ctx := context.Background()
app, err := NewApp(ctx, cfg, ":memory:")
if err != nil {
@@ -197,6 +214,12 @@ func TestAppRun_GracefulShutdown(t *testing.T) {
},
}
prevFactory := navidromeClientFactory
navidromeClientFactory = func(c config.NavidromeConfig) (*navidrome.NavidromeClient, error) {
return navidrome.NewClientUnauthenticated(c), nil
}
defer func() { navidromeClientFactory = prevFactory }()
ctx, cancel := context.WithCancel(context.Background())
app, err := NewApp(ctx, cfg, ":memory:")