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); } }