Commit Graph
4 Commits
Author SHA1 Message Date
Admin 56e06ccd5f Add a config toggle to enable/disable the YouTube Music source
- YouTubeConfiguration.Enabled (default true, Infrastructure): single
  switch for the whole source
- DisabledYouTubeMusicService: null-object IYouTubeMusicService that
  fails every call with Forbidden, registered instead of the real one
  when disabled (AddYouTubeMusicService) so no client is ever built
- AppSettings.json (Api + Worker): document YouTube.Enabled explicitly;
  overridable via YouTube__Enabled env var like every other section
- CurrentUser.cs: small unrelated cleanup, collapse the IsAuthenticated
  computation to a single null-coalescing expression

Search, albums, stream resolution and adding a new YouTube track all
surface as 403 Forbidden through the existing ErrorOr -> HTTP mapping.
Daily mix generation already treated a YouTube search failure as "no
YouTube candidates" and keeps working with local-only candidates —
added a test locking that in. Already-downloaded YouTube tracks are
unaffected since playback never goes through IYouTubeMusicService.

257/257 tests passing (7 new), 0 build warnings.
2026-09-09 23:00:15 +02:00
Admin b57949a6bc Fix test bad practices and tighten weak assertions
- StorageServiceTests: replace out-param + manual dispose with IAsyncLifetime
- PictureServiceTests: Assert.True bound checks -> Assert.InRange
- CreateAlbumCommandHandlerTests: assert the specific NotFound error type
  instead of just IsError, matching what the test name promises
- CreateAlbumRequestValidatorTests: replace a boundary Theory that computed
  its expected result inline with explicit Facts against the real
  AlbumReleaseYear constants
- Remove unused using directives across a handful of production and test
  files found during the review

Full suite verified green: 250/250 tests passing, 0 build warnings.
2026-09-09 21:53:00 +02:00
Admin a26f4b43fd Add Infrastructure/Api/StreamingGateway test projects
Second pass on backend/Tests/, covering what Domain+Application (the first
pass) deliberately left out — the parts that talk to real infrastructure:

  Musify.Infrastructure.Tests   Database/migrations, StorageService,
                                 StreamTicketService, AuditableEntityInterceptor,
                                 PictureService, SingleFlightCache,
                                 PlayListPresetSeeder.
  Musify.Api.Tests               Real HTTP endpoints via WebApplicationFactory
                                 (routing, auth, ValidationFilter, ErrorOr->HTTP
                                 mapping), plus isolated unit tests for
                                 ErrorOrHttpExtensions, CurrentUser and the
                                 FluentValidation validators.
  Musify.StreamingGateway.Tests  TicketValidator (RS256) and
                                 TicketValidationMiddleware (path traversal,
                                 prefix boundary, ticket extraction).

Musify.Worker gets no test project: its Program.cs is pure DI/host wiring
(Hangfire, MassTransit, jobs); the logic it runs lives in Musify.Infrastructure,
already covered there.

Per the user's direction, the parts that touch real infrastructure use
Testcontainers instead of fakes: a real Postgres (Testcontainers.PostgreSql)
for Infrastructure.Tests and Api.Tests, and — matching deploy/compose.yml
exactly (chrislusf/seaweedfs:latest, same startup command), not MinIO — a
real SeaweedFS container for StorageService. Api.Tests boots the actual
Musify.Api host against that Postgres (real routing/auth/validation) but
substitutes the services that talk over the network (storage, YouTube, the
message bus, stream-ticket signing) with NSubstitute fakes, and swaps the
JwtBearer scheme for a test-only header-based one (FakeAuthenticationHandler)
so tests don't need a reachable Zitadel. Application.Tests' approach — a real
DbContext (SQLite in-memory) rather than mocking IDatabase, since it exposes
DbSet<T> directly and handlers compose real LINQ queries against it — carries
over into Infrastructure.Tests/Api.Tests via real Postgres instead.

Testing against the real backend (not a fake) found two genuine production
bugs, both fixed here:

- StorageService.RemoveFolderAsync threw NullReferenceException against
  SeaweedFS: its DeleteObjects response omits the <Error> list entirely when
  nothing failed, which the AWS SDK deserializes as null rather than an empty
  list (real AWS S3 always returns one). Now treats null as no errors.
- AuditableEntityInterceptor never bumped UpdatedAt on a normal update (load,
  mutate a property, SaveChanges) — only on insert. SavingChanges(Async) fires
  before EF's own automatic change detection for that save, so an entity
  modified through a plain property setter still reported EntityState.Unchanged
  inside the interceptor. Now calls ChangeTracker.DetectChanges() explicitly
  first, which is the documented fix for exactly this interceptor pattern.

Also required to make the Api host testable: Program.cs now ends with `public
partial class Program;` (WebApplicationFactory<Program> needs the top-level-
statements Program class to be public — no behavior change), and
Directory.Packages.props gains Microsoft.AspNetCore.Mvc.Testing, Testcontainers
and Testcontainers.PostgreSql.

249 tests total across all five projects, all green: `dotnet test
backend/Musify.slnx` (needs Docker running for Infrastructure.Tests and
Api.Tests).
2026-09-09 21:39:06 +02:00
Admin 89569736b7 Reorganize backend to mirror Musify.slnx folder structure
Moves the six backend projects on disk into Core/ and Hosts/
subfolders, matching the solution folders already defined in
Musify.slnx, so the file explorer reflects the same layout as the
solution:

  backend/Core/Musify.Domain
  backend/Core/Musify.Application
  backend/Core/Musify.Infrastructure
  backend/Hosts/Musify.Api
  backend/Hosts/Musify.Worker
  backend/Hosts/Musify.StreamingGateway

Updates every reference to the old paths: ProjectReference entries in
Musify.Api.csproj and Musify.Worker.csproj, the three Dockerfiles,
deploy/scripts/migrate.ps1|sh, deploy/zitadel/provision.ps1,
deploy/up.ps1, deploy/README.md and docs/development.md. Also fixes a
stale Dockerfile comment pointing at a compose file that no longer
exists.

Verified with `dotnet build Musify.slnx` against the new layout.
2026-09-09 14:27:50 +02:00