package main import ( "context" "errors" "strings" "testing" "time" "github.com/jackc/pgx/v5/pgxpool" ) func TestLoadConfigConcurrency(t *testing.T) { values := map[string]string{"VIDEO_DIR": "/media", "YT_DLP_PATH": "/bin/downloader", "FFPROBE_PATH": "/bin/probe"} getenv := func(key string) string { return values[key] } config, err := loadConfig(getenv) if err != nil || config.Concurrency != 1 || config.VideoDir != "/media" || config.YTDLPPath != "/bin/downloader" || config.FFprobePath != "/bin/probe" { t.Fatalf("既定設定: %+v %v", config, err) } values["WORKER_CONCURRENCY"] = "2" config, err = loadConfig(getenv) if err != nil || config.Concurrency != 2 { t.Fatalf("並行数2: %+v %v", config, err) } for _, invalid := range []string{"0", "3", "bad"} { values["WORKER_CONCURRENCY"] = invalid if _, err := loadConfig(getenv); err == nil { t.Errorf("不正な並行数 %q を許した", invalid) } } } func TestInitializeDatabaseRequiresURLMigratesAndRequeues(t *testing.T) { ctx := context.Background() now := time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC) connectCalls, migrateCalls, requeueCalls := 0, 0, 0 connect := func(_ context.Context, url string) (*pgxpool.Pool, error) { connectCalls++ if url != "postgres://test:secret@localhost/test" { t.Errorf("URL=%q", url) } return pgxpool.New(ctx, url) } migrate := func(context.Context, *pgxpool.Pool) error { migrateCalls++; return nil } requeue := func(_ context.Context, _ *pgxpool.Pool, cutoff time.Time) (int64, error) { requeueCalls++ if want := now.Add(-staleAfter); !cutoff.Equal(want) { t.Errorf("cutoff=%v want=%v", cutoff, want) } return 2, nil } if _, err := initializeDatabase(ctx, " ", now, connect, migrate, requeue); err == nil || !strings.Contains(err.Error(), "DATABASE_URL") { t.Fatalf("未設定 URL: %v", err) } if connectCalls != 0 || migrateCalls != 0 || requeueCalls != 0 { t.Fatalf("未設定時の呼び出し: %d %d %d", connectCalls, migrateCalls, requeueCalls) } pool, err := initializeDatabase(ctx, "postgres://test:secret@localhost/test", now, connect, migrate, requeue) if err != nil || pool == nil { t.Fatalf("初期化: %v", err) } pool.Close() if connectCalls != 1 || migrateCalls != 1 || requeueCalls != 1 { t.Fatalf("呼び出し回数: %d %d %d", connectCalls, migrateCalls, requeueCalls) } } func TestInitializeDatabaseStopsOnMigrationOrRequeueFailure(t *testing.T) { ctx := context.Background() connect := func(context.Context, string) (*pgxpool.Pool, error) { return pgxpool.New(ctx, "postgres://localhost/test") } requeueCalled := false pool, err := initializeDatabase(ctx, "postgres://localhost/test", time.Now(), connect, func(context.Context, *pgxpool.Pool) error { return errors.New("migration failed") }, func(context.Context, *pgxpool.Pool, time.Time) (int64, error) { requeueCalled = true; return 0, nil }) if pool != nil || err == nil || !strings.Contains(err.Error(), "migration failed") || requeueCalled { t.Fatalf("migration failure: pool=%v err=%v requeue=%v", pool, err, requeueCalled) } pool, err = initializeDatabase(ctx, "postgres://localhost/test", time.Now(), connect, func(context.Context, *pgxpool.Pool) error { return nil }, func(context.Context, *pgxpool.Pool, time.Time) (int64, error) { return 0, errors.New("requeue failed") }) if pool != nil || err == nil || !strings.Contains(err.Error(), "requeue failed") { t.Fatalf("requeue failure: pool=%v err=%v", pool, err) } } func TestInitializeDatabaseDoesNotLeakURL(t *testing.T) { _, err := initializeDatabase(context.Background(), "postgres://user:secret@localhost/test", time.Now(), func(context.Context, string) (*pgxpool.Pool, error) { return nil, errors.New("postgres://user:secret@localhost/test refused") }, func(context.Context, *pgxpool.Pool) error { return nil }, func(context.Context, *pgxpool.Pool, time.Time) (int64, error) { return 0, nil }) if err == nil || strings.Contains(err.Error(), "secret") { t.Fatalf("URL 漏えい: %v", err) } }