Files
Musify/docs/projects.md
T
Admin 611d9cd436 Add backend test suite under backend/Tests/
Two xUnit projects, wired into Musify.slnx under a /Tests/ folder that
mirrors Core/ and Hosts/:

  Musify.Domain.Tests        Value-object logic (TrackAudio/TrackPictures'
                              IsProcessed/IsFailed/IsInProgress). Entities are
                              otherwise anemic EF models with no behavior to
                              test.
  Musify.Application.Tests   All ~45 Application handlers and services
                              (Albums, Tracks, PlayLists, Users, Mixes,
                              YouTube, plus TrackStreamIssuer,
                              UploadIntentValidator, YouTubeTrackProvisioner).

IDatabase exposes DbSet<T> directly, so handlers compose real LINQ queries
against it — that can't be expressed with a mock (no IQueryable provider
behind one). TestSupport/TestDatabase.cs is instead a real DbContext on a
private SQLite in-memory connection per test (fresh per test since xUnit
instantiates the test class per test method): fast, no external dependency,
and — unlike EF Core's InMemory provider — SQLite enforces real foreign keys
and supports transactions, which two handlers need. External dependencies
(IStorageService, IEventBus, IYouTubeMusicService, ...) are substituted with
NSubstitute.

TestSupport/TestEntities.cs and TestConfigurations.cs build fully-populated
domain entities and configuration POCOs with sensible defaults for every
required member, so no test method needs to fill in properties it doesn't
care about, and no builder ever hands back null.

Also, needed to make the Application layer itself testable without a real
Postgres or user-secrets:
- DatabaseDesignTimeFactory now falls back to the Database__ConnectionString
  env var, and DesignSettings.json is optional — unblocks the `migrate`
  one-shot container from deploy/compose.yml, which hit the same gap.
- Directory.Packages.props: xunit, xunit.runner.visualstudio, NSubstitute,
  Microsoft.EntityFrameworkCore.Sqlite (pinned to the same 10.0.8 as the rest
  of EF Core), Microsoft.NET.Test.Sdk, coverlet.collector. Also pins
  SQLitePCLRaw.bundle_e_sqlite3 to 2.1.13 — EFCore.Sqlite 10.0.8 pulls 2.1.11
  transitively, which has a known high-severity advisory (GHSA-2m69-gcr7-jv3q).
- backend/.dockerignore excludes Tests/: no application Dockerfile needs it
  in its build context.

162 tests, all green: `dotnet test backend/Musify.slnx`.
2026-09-09 18:29:05 +02:00

4.2 KiB

Proyectos

Los proyectos .NET viven en backend/ (solución Musify.slnx, carpetas Core/, Hosts/ y Tests/); el cliente web en web-player/.

Musify.Api (:5111)

API REST pública (Minimal APIs, .NET 10). Es la fachada fina del sistema.

  • Endpoints: UserEndpoints, TrackEndpoints, PlayListEndpoints.
  • Autenticación: JWT Bearer contra Zitadel (Authentication/), con JwtBearerEventsHandler que sincroniza el usuario en cada token validado.
  • CurrentUser: abstracción que se enlaza como parámetro de endpoint (IBindableFromHttpContext) y expone los claims básicos (Id como long?, RequiredId, nombre, etc.) sin tener que leer ClaimsPrincipal a mano.
  • Emite: presigned URLs de subida (vía IStorageService) y stream-tickets (vía IStreamTicketService, RS256).
  • OpenAPI + Scalar para documentación/prueba; OAuth2 (PKCE) configurado para login desde Scalar.
  • CORS totalmente abierto solo en Development (para el front local).
  • Traduce ErrorOr → HTTP en ResultHttpExtensions.

Propósito/porqué: separar la cara pública (autorización + metadatos) del procesado y del servido de bytes.

Musify.Domain + Musify.Application + Musify.Infrastructure

El núcleo, en tres proyectos (clean architecture):

Domain

Entidades (Entities/) y value objects/enums (ValueObjects/: ProcessingStatus, LifeCycleStatus, UploadIntentStatus, …). Sin dependencias de infraestructura.

Application

Casos de uso (CQRS con Mediator):

  • Tracks/, PlayLists/, Users/ → Commands, Queries, Handlers, Responses.
  • Contracts/ → interfaces que implementa la infraestructura (IDatabase, IStorageService, IAudioTranscoderService, IPictureService, IEventBus, IStreamTicketService).
  • Configuration/ → opciones tipadas (Track, PlayList, ApplicationStorage, UploadIntent…) bindeadas y validadas al arranque.
  • Shared/ → helpers transversales (StorageKey.Combine, ImageSize).
  • Pagination/ → PaginatedResponse<T>.

Porqué: la lógica de negocio no conoce EF, S3 ni RabbitMQ; solo interfaces. Reutilizable por la API y el Worker.

Infrastructure

Implementaciones concretas:

  • Persistence/ → Database (EF Core/Npgsql), migraciones.
  • Services/ → StorageService (AWSSDK.S3 contra SeaweedFS), AudioTranscoderService (ffmpeg → .m4a), PictureService (ImageSharp), StreamTicketService (firma RS256).
  • MassTransit/ → consumers, activities y routing slips (ver media-processing.md).
  • Jobs/ → UploadIntentExpirationJob, TemporalUploadsCleanUpJob.

Musify.StreamingGateway (:8081)

Reverse proxy (ASP.NET + YARP) que sirve el audio (.m4a) desde SeaweedFS. Valida el stream-ticket (RS256, clave pública) y, si el objeto pedido cae bajo el prefijo autorizado, reenvía al filer.

Porqué un proyecto aparte: escala con el tráfico de bytes independientemente de la API, tiene superficie mínima (solo sabe validar un JWT y proxiar) y permite que SeaweedFS quede en red privada. Ver streaming.md.

Musify.Worker

Worker Service (host de fondo) que consume los eventos de MassTransit y ejecuta el procesado pesado: transcode de audio a .m4a (ffmpeg), generación de miniaturas (ImageSharp), transferencias de ficheros en el bucket, y los jobs de expiración/limpieza de upload intents.

Porqué aparte: el trabajo CPU/IO no debe ocurrir dentro de un request HTTP; se escala y reinicia por separado. (Antes se llamaba Musify.PictureWorker; se renombró porque hace más que imágenes.)

web-player (:5173 en dev, :3000 en contenedor)

Cliente web (SvelteKit 2 + Svelte 5 + Tailwind 4). La sesión OIDC vive en el servidor (cookie cifrada); las llamadas al backend salen del servidor con el access token, y las subidas se hacen contra las URLs prefirmadas de S3.

Musify.Domain.Tests + Musify.Application.Tests

backend/Tests/, carpeta /Tests/ en el .slnx. xUnit, sin Docker ni Postgres real — Application.Tests corre los handlers contra SQLite en memoria (ver development.md).

Otras carpetas

  • deploy/ — stacks de Docker Compose (dev y prod), autocontenidos. Ver ../deploy/README.md.
  • docs/ — esta documentación.