using Microsoft.Extensions.Logging.Abstractions;
using SameBeat.Application.DTO;
using SameBeat.Application.Interfejsy;
using SameBeat.Application.Komendy;
using SameBeat.Application.Serwisy;
using SameBeat.Domain.Encje;
using SameBeat.Domain.Enumy;
using Xunit;
namespace SameBeat.Tests.Unit;
///
/// Testy SerwisMuzyki -- biblioteka, playlista i "zegar" odtwarzania. Zaleznosci od bazy danych sa
/// zastapione prostymi fejkami w pamieci (projekt nie uzywa biblioteki do mockowania), tak samo jak
/// SerwisScenyTests.
///
public class SerwisMuzykiTests
{
private sealed class FejkRepozytoriumWydarzen : IRepozytoriumWydarzen
{
private readonly List _wydarzenia = new();
public Task PobierzOstatnieAsync(CancellationToken token = default) =>
Task.FromResult(_wydarzenia.LastOrDefault());
public Task ZnajdzPoIdAsync(Guid id, CancellationToken token = default) =>
Task.FromResult(_wydarzenia.SingleOrDefault(w => w.Id == id));
public void Dodaj(Wydarzenie wydarzenie) => _wydarzenia.Add(wydarzenie);
}
private sealed class FejkRepozytoriumUtworow : IRepozytoriumUtworow
{
private readonly List _utwory = new();
public Task ZnajdzPoIdAsync(Guid id, CancellationToken token = default) =>
Task.FromResult(_utwory.SingleOrDefault(u => u.Id == id));
public Task> PobierzWszystkieAsync(CancellationToken token = default) =>
Task.FromResult(_utwory.OrderBy(u => u.Tytul).ToList());
public void Dodaj(Utwor utwor) => _utwory.Add(utwor);
}
private sealed class FejkRepozytoriumPlaylisty : IRepozytoriumPlaylisty
{
private readonly List _pozycje = new();
public Task ZnajdzPoIdAsync(Guid pozycjaId, CancellationToken token = default) =>
Task.FromResult(_pozycje.SingleOrDefault(p => p.Id == pozycjaId));
public Task> PobierzUporzadkowanaDlaWydarzeniaAsync(Guid wydarzenieId, CancellationToken token = default) =>
Task.FromResult(_pozycje.Where(p => p.WydarzenieId == wydarzenieId).OrderBy(p => p.Kolejnosc).ToList());
public void Dodaj(PozycjaPlaylisty pozycja) => _pozycje.Add(pozycja);
public void Usun(PozycjaPlaylisty pozycja) => _pozycje.Remove(pozycja);
}
private sealed class FejkJednostkaPracy : IJednostkaPracy
{
public Task ZapiszZmianyAsync(CancellationToken token = default) => Task.FromResult(0);
}
private readonly FejkRepozytoriumWydarzen _repozytoriumWydarzen = new();
private readonly FejkRepozytoriumUtworow _repozytoriumUtworow = new();
private readonly FejkRepozytoriumPlaylisty _repozytoriumPlaylisty = new();
private readonly SerwisMuzyki _serwis;
public SerwisMuzykiTests()
{
_serwis = new SerwisMuzyki(
_repozytoriumWydarzen,
_repozytoriumUtworow,
_repozytoriumPlaylisty,
new FejkJednostkaPracy(),
NullLogger.Instance);
}
private Wydarzenie DodajWydarzenie()
{
var wydarzenie = Wydarzenie.Utworz("Testowa impreza");
_repozytoriumWydarzen.Dodaj(wydarzenie);
return wydarzenie;
}
private Utwor DodajUtwor(string tytul = "Utwor")
{
var utwor = Utwor.Utworz(tytul, "Wykonawca", "https://przyklad.pl/" + Guid.NewGuid() + ".mp3", 60_000);
_repozytoriumUtworow.Dodaj(utwor);
return utwor;
}
private async Task DodajDoPlaylistyAsync(Guid wydarzenieId, Guid utworId)
{
var wynik = await _serwis.DodajDoPlaylistyAsync(utworId);
Assert.True(wynik.Sukces);
return wynik.Dane!.Single(p => p.Utwor.Id == utworId);
}
// ---- DodajUtworAsync -- walidacja ----
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task DodajUtworAsync_PustyTytul_ZwracaNiepowodzenie(string tytul)
{
var wynik = await _serwis.DodajUtworAsync(new DodajUtworKomenda(tytul, null, "https://przyklad.pl/a.mp3", 1000));
Assert.False(wynik.Sukces);
}
[Theory]
[InlineData("")]
[InlineData("nie-jest-to-url")]
public async Task DodajUtworAsync_NiepoprawnyUrl_ZwracaNiepowodzenie(string url)
{
var wynik = await _serwis.DodajUtworAsync(new DodajUtworKomenda("Tytul", null, url, 1000));
Assert.False(wynik.Sukces);
}
[Theory]
[InlineData(0)]
[InlineData(-100)]
public async Task DodajUtworAsync_NiedodatniCzasTrwania_ZwracaNiepowodzenie(int czasTrwaniaMs)
{
var wynik = await _serwis.DodajUtworAsync(new DodajUtworKomenda("Tytul", null, "https://przyklad.pl/a.mp3", czasTrwaniaMs));
Assert.False(wynik.Sukces);
}
[Fact]
public async Task DodajUtworAsync_PoprawneDane_DodajeIZwracaUtwor()
{
var wynik = await _serwis.DodajUtworAsync(new DodajUtworKomenda("Tytul", "Wykonawca", "https://przyklad.pl/a.mp3", 180_000));
Assert.True(wynik.Sukces);
Assert.Equal("Tytul", wynik.Dane!.Tytul);
Assert.Equal(180_000, wynik.Dane.CzasTrwaniaMs);
}
// ---- PobierzStanAsync ----
[Fact]
public async Task PobierzStanAsync_BrakWydarzenia_ZwracaCiszeNieBlad()
{
var wynik = await _serwis.PobierzStanAsync();
Assert.True(wynik.Sukces);
Assert.Equal(StanOdtwarzania.Zatrzymana, wynik.Dane!.Stan);
Assert.Null(wynik.Dane.UtworId);
}
// ---- OdtworzAsync ----
[Fact]
public async Task OdtworzAsync_ZPodanymUtworId_PrzelaczaOdZera()
{
var wydarzenie = DodajWydarzenie();
var utwor = DodajUtwor();
var wynik = await _serwis.OdtworzAsync(utwor.Id);
Assert.True(wynik.Sukces);
Assert.Equal(utwor.Id, wynik.Dane!.UtworId);
Assert.Equal(0, wynik.Dane.PozycjaStartowaMs);
Assert.Equal(StanOdtwarzania.Odtwarzana, wynik.Dane.Stan);
Assert.Equal(utwor.Tytul, wynik.Dane.Tytul);
Assert.Equal(utwor.Url, wynik.Dane.Url);
}
[Fact]
public async Task OdtworzAsync_NieistniejacyUtworId_ZwracaNiepowodzenie()
{
DodajWydarzenie();
var wynik = await _serwis.OdtworzAsync(Guid.NewGuid());
Assert.False(wynik.Sukces);
}
[Fact]
public async Task OdtworzAsync_BezUtworIdGdyWstrzymana_WznawiaOdZapisanejPozycji()
{
var wydarzenie = DodajWydarzenie();
var utwor = DodajUtwor();
await _serwis.OdtworzAsync(utwor.Id);
Thread.Sleep(50);
await _serwis.WstrzymajAsync();
var pozycjaPoPauzie = wydarzenie.PozycjaStartowaMs;
var wynik = await _serwis.OdtworzAsync(null);
Assert.True(wynik.Sukces);
Assert.Equal(StanOdtwarzania.Odtwarzana, wynik.Dane!.Stan);
Assert.Equal(pozycjaPoPauzie, wynik.Dane.PozycjaStartowaMs);
Assert.NotNull(wynik.Dane.CzasStartuUtc);
}
[Fact]
public async Task OdtworzAsync_BezUtworIdGdyNicNieWybrane_ZwracaNiepowodzenie()
{
DodajWydarzenie();
var wynik = await _serwis.OdtworzAsync(null);
Assert.False(wynik.Sukces);
}
// ---- WstrzymajAsync -- przeliczanie pozycji (zegar sie zgadza) ----
[Fact]
public async Task WstrzymajAsync_PrzeliczaPozycjeNaPodstawieUplynietegoCzasu()
{
var wydarzenie = DodajWydarzenie();
var utwor = DodajUtwor();
await _serwis.OdtworzAsync(utwor.Id);
Thread.Sleep(200);
var wynik = await _serwis.WstrzymajAsync();
Assert.True(wynik.Sukces);
Assert.Equal(StanOdtwarzania.Wstrzymana, wynik.Dane!.Stan);
Assert.Null(wynik.Dane.CzasStartuUtc);
Assert.InRange(wynik.Dane.PozycjaStartowaMs, 150, 5000);
}
// ---- Nastepny / Poprzedni -- granice kolejki ----
[Fact]
public async Task NastepnyAsync_PustaPlaylista_ZwracaNiepowodzenie()
{
DodajWydarzenie();
var wynik = await _serwis.NastepnyAsync();
Assert.False(wynik.Sukces);
}
[Fact]
public async Task NastepnyAsync_NicNieGra_StartujeOdPierwszejPozycji()
{
var wydarzenie = DodajWydarzenie();
var utwor1 = DodajUtwor("A");
var utwor2 = DodajUtwor("B");
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor1.Id);
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor2.Id);
var wynik = await _serwis.NastepnyAsync();
Assert.True(wynik.Sukces);
Assert.Equal(utwor1.Id, wynik.Dane!.UtworId);
Assert.Equal(StanOdtwarzania.Odtwarzana, wynik.Dane.Stan);
}
[Fact]
public async Task NastepnyAsync_WSrodkuKolejki_PrzechodziDoKolejnego()
{
var wydarzenie = DodajWydarzenie();
var utwor1 = DodajUtwor("A");
var utwor2 = DodajUtwor("B");
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor1.Id);
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor2.Id);
await _serwis.OdtworzAsync(utwor1.Id);
var wynik = await _serwis.NastepnyAsync();
Assert.True(wynik.Sukces);
Assert.Equal(utwor2.Id, wynik.Dane!.UtworId);
Assert.Equal(0, wynik.Dane.PozycjaStartowaMs);
}
[Fact]
public async Task NastepnyAsync_NaOstatnimUtworze_ZatrzymujeIUtworIdJestNull()
{
var wydarzenie = DodajWydarzenie();
var utwor1 = DodajUtwor("A");
var utwor2 = DodajUtwor("B");
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor1.Id);
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor2.Id);
await _serwis.OdtworzAsync(utwor2.Id); // ostatni
var wynik = await _serwis.NastepnyAsync();
Assert.True(wynik.Sukces);
Assert.Null(wynik.Dane!.UtworId);
Assert.Equal(StanOdtwarzania.Zatrzymana, wynik.Dane.Stan);
}
[Fact]
public async Task PoprzedniAsync_NaPierwszymUtworze_ZatrzymujeIUtworIdJestNull()
{
var wydarzenie = DodajWydarzenie();
var utwor1 = DodajUtwor("A");
var utwor2 = DodajUtwor("B");
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor1.Id);
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor2.Id);
await _serwis.OdtworzAsync(utwor1.Id); // pierwszy
var wynik = await _serwis.PoprzedniAsync();
Assert.True(wynik.Sukces);
Assert.Null(wynik.Dane!.UtworId);
Assert.Equal(StanOdtwarzania.Zatrzymana, wynik.Dane.Stan);
}
[Fact]
public async Task PoprzedniAsync_WSrodkuKolejki_PrzechodziDoPoprzedniego()
{
var wydarzenie = DodajWydarzenie();
var utwor1 = DodajUtwor("A");
var utwor2 = DodajUtwor("B");
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor1.Id);
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor2.Id);
await _serwis.OdtworzAsync(utwor2.Id);
var wynik = await _serwis.PoprzedniAsync();
Assert.True(wynik.Sukces);
Assert.Equal(utwor1.Id, wynik.Dane!.UtworId);
}
// ---- UstawGlosnoscAsync -- walidacja zakresu ----
[Theory]
[InlineData(-0.01f)]
[InlineData(1.5f)]
public async Task UstawGlosnoscAsync_PozaZakresem_ZwracaNiepowodzenie(float wartosc)
{
DodajWydarzenie();
var wynik = await _serwis.UstawGlosnoscAsync(wartosc);
Assert.False(wynik.Sukces);
}
[Fact]
public async Task UstawGlosnoscAsync_WZakresie_UstawiaINieRuszaStanuOdtwarzania()
{
var wydarzenie = DodajWydarzenie();
var utwor = DodajUtwor();
await _serwis.OdtworzAsync(utwor.Id);
var wynik = await _serwis.UstawGlosnoscAsync(0.3f);
Assert.True(wynik.Sukces);
Assert.Equal(0.3f, wynik.Dane!.GlosnoscMaster);
Assert.Equal(StanOdtwarzania.Odtwarzana, wynik.Dane.Stan);
Assert.Equal(utwor.Id, wynik.Dane.UtworId);
}
// ---- Playlista: dodaj / usun ----
[Fact]
public async Task DodajDoPlaylistyAsync_DodajeNaKoniecZRosnacaKolejnoscia()
{
var wydarzenie = DodajWydarzenie();
var utwor1 = DodajUtwor("A");
var utwor2 = DodajUtwor("B");
await _serwis.DodajDoPlaylistyAsync(utwor1.Id);
var wynik = await _serwis.DodajDoPlaylistyAsync(utwor2.Id);
Assert.True(wynik.Sukces);
Assert.Equal(2, wynik.Dane!.Count);
Assert.Equal(0, wynik.Dane[0].Kolejnosc);
Assert.Equal(1, wynik.Dane[1].Kolejnosc);
}
[Fact]
public async Task DodajDoPlaylistyAsync_NieistniejacyUtwor_ZwracaNiepowodzenie()
{
DodajWydarzenie();
var wynik = await _serwis.DodajDoPlaylistyAsync(Guid.NewGuid());
Assert.False(wynik.Sukces);
}
[Fact]
public async Task UsunZPlaylistyAsync_UsuwaPozycje()
{
var wydarzenie = DodajWydarzenie();
var utwor = DodajUtwor();
var pozycja = await DodajDoPlaylistyAsync(wydarzenie.Id, utwor.Id);
var wynik = await _serwis.UsunZPlaylistyAsync(pozycja.Id);
Assert.True(wynik.Sukces);
Assert.Empty(wynik.Dane!);
}
[Fact]
public async Task UsunZPlaylistyAsync_NieistniejacaPozycja_JestIdempotentna()
{
DodajWydarzenie();
var wynik = await _serwis.UsunZPlaylistyAsync(Guid.NewGuid());
Assert.True(wynik.Sukces);
}
[Fact]
public async Task PobierzPlaylisteAsync_OznaczaAktualnieGranaPozycje()
{
var wydarzenie = DodajWydarzenie();
var utwor1 = DodajUtwor("A");
var utwor2 = DodajUtwor("B");
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor1.Id);
await DodajDoPlaylistyAsync(wydarzenie.Id, utwor2.Id);
await _serwis.OdtworzAsync(utwor2.Id);
var wynik = await _serwis.PobierzPlaylisteAsync();
Assert.True(wynik.Sukces);
Assert.False(wynik.Dane!.Single(p => p.Utwor.Id == utwor1.Id).CzyAktualnieGrana);
Assert.True(wynik.Dane!.Single(p => p.Utwor.Id == utwor2.Id).CzyAktualnieGrana);
}
}