V2 New features#462
Conversation
1. TMDB sync — ReferenceSyncBackgroundService runs in-process (no Kubernetes CronJob needed) every 24h, re-checking reference docs older than 3 days. It calls TMDB's cheap per-id /changes endpoint first and only does the expensive full re-fetch if something actually changed. Admins also get a "Sync now" button on the Reference Data admin page for an immediate forced check of everything. 2. Future/unaired episodes — excluded from the TV show detail page's episode checklist (same air-date filter Watch Next already used), so an announced-but-unaired season no longer shows checkboxes for episodes that haven't happened yet. 3. Unique TMDB id constraint — added to tvshow_reference/movie_reference/person_reference in mongodb-create-index.js (partial + unique, safe for older documents missing the field). 4. Watch Next movies — now excludes movies already marked watched, even if still flagged "want to watch" (that flag doesn't auto-clear on watch the way the TV Time import path does). 5. All emojis replaced — every color emoji sitewide (nav icons, home page pills, dropzone, dismiss button, etc.) swapped for plain symbols confirmed to have non-emoji default presentation (this caught a real bug: the old ⭐/👁 icons were never actually plain-text despite looking like it). 6. Renamed — "Best of" → Favorites, "Want to watch" → Watchlist, everywhere (buttons, list columns, forms, modals), no icons. 7. Watched flag — now green instead of yellow. 8. Favorites/Watchlist buttons — replaced the harsh solid blue/white btn-primary selected state with a soft accent-tinted pill, consistent in both themes. One thing I caught along the way: registering the new background sync service made every integration test spin up its own host that fired live TMDB calls against shared test data. I gated it behind a Features:IsReferenceSyncEnabled flag (on by default, off in the test host) so tests stay deterministic — documented in CLAUDE.md along with the rest.
Phase 1 (storage boundary): Added Riok.Mapperly, created IStorageMapper<TModel,TEntity> + CommonStorageMappings (shared DateOnly<->DateTime UTC conversion), and 18 storage mapper classes in Infrastructure.MongoDb/Mappers/ (12 generated via [Mapper], CarHistoryStorageMapper hand-written for the bespoke Location/Fuel/Station flattening). Switched MongoDbRepositoryBase and all 18 repositories off IMapper, deleted the three *DataStorageMappingProfile classes. Phase 2 (web boundary): Added IDtoMapper<TDto,TModel> + CommonDtoMappings (the ?? string.Empty fallback for nullable-DTO-to-required-model strings), 12 CRUD DTO mappers plus 8 one-directional Model→Dto mappers (WatchNext, Car/House metrics, the five reference-data types). Handled the one enum+nullability edge case (CarDto.EnergyType) with a hand-written null fallback wrapping a Mapperly-generated ByName enum conversion. Switched every controller off IMapper, deleted WebServiceMappingProfile. Phase 3 (removal/hardening): Removed AddAutoMapper, the AutoMapper package everywhere, and AutoMapperConfigurationTest (its job is now a compile-time check). Escalated Mapperly's RMG012/RMG020 to build errors in .editorconfig. Updated CLAUDE.md, CONTRIBUTING.md, and docs/code-quality-findings.md to reflect the new null-preserving semantics and reframe the old AllowNullDestinationValues gotchas as historical.
️✅ There are no secrets present in this pull request anymore.If these secrets were true positive and are still valid, we highly recommend you to revoke them. 🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request. |
| process.exit(1); | ||
| } | ||
| const rows = fs | ||
| .readFileSync(path, 'utf8') |
| try | ||
| { | ||
| Guid jobId; | ||
| await using (var stream = e.File.OpenReadStream(MaxFileSize)) |
|
|
||
| try | ||
| { | ||
| await using var stream = e.File.OpenReadStream(MaxFileSize); |
| _importResult = null; | ||
| try | ||
| { | ||
| await using var stream = e.File.OpenReadStream(MaxImportFileSize); |
| /// hundred rows complete well within a normal request. | ||
| /// </summary> | ||
| [HttpPost("car-history")] | ||
| [RequestSizeLimit(10_000_000)] |
| /// as an upsert. Runs in the background; poll <see cref="GetStatus"/> with the returned job id for progress. | ||
| /// </summary> | ||
| [HttpPost("tv-time")] | ||
| [RequestSizeLimit(50_000_000)] |
|
|
||
| public async Task<BookDetails?> GetBookDetailsAsync(string externalId, CancellationToken cancellationToken = default) | ||
| { | ||
| var work = await http.GetFromJsonAsync<OpenLibraryWorkResponse>($"{externalId}.json", cancellationToken); |
| /// twice is a no-op the second time, since every document already carries the id it was exported with. | ||
| /// </summary> | ||
| [HttpPost("import")] | ||
| [RequestSizeLimit(50_000_000)] |
| /// Fellowship of the Ring" (OL27513W). The year is the last such token, since it's always what trails | ||
| /// the day/month when both are present, and the only token when the date is a bare year. | ||
| /// </summary> | ||
| private static readonly Regex s_yearRegex = new(@"\b\d{4}\b", RegexOptions.Compiled); |
What I reviewed and how it shaped the plan: From todo-blazor — I kept the Microsoft.Playwright.Xunit.v3 PageTest + plain xunit facts style, the page-object pattern (locators/actions split, WaitForReadyAsync, chained typed navigation), and the Kestrel-factory in-process hosting. I dropped Reqnroll/Gherkin (your project has both styles side by side, and the Gherkin one is roughly double the code for the same coverage) and the Metalama screenshot aspect (xunit v3's TestContext.Current.TestState gives failure detection in DisposeAsync for free). Keeptrack-specific findings that drove the design: - The login page is OAuth-popup-only (GitHub/Google), which Playwright can't automate — but POST /auth/callback accepts any verified Firebase ID token, so auth is programmatic: REST signInWithPassword (reusing the integration tests' AccountRepository), post the token, save the cookie as Playwright storage state. Every test starts signed in. - By default the fixture creates an ephemeral admin user via the Firebase Admin SDK (Firebase__ServiceAccount is already required by the Blazor host, so no new secrets), seeds data over the REST API with the shared WebApi.Contracts DTOs, and populates references via the admin import endpoint with a synthetic in-memory fixture — the "look for a ref" smoke test then uses the "check for reference match" button, which never calls TMDB/Open Library, so the suite is fully deterministic with zero provider keys. - Hosting both apps in-process hits a real gotcha: both src/WebApi and src/BlazorApp generate a global Program class, so the test project needs extern alias on the project references. - The existing KestrelWebAppFactory gets extracted to a small test/Testing.Shared project (no duplicated logic, per the quality bar), parameterized so the e2e project can compose two instances with WebApi:BaseUrl injected. Your mid-task additions are covered: every knob is an env var (E2E_ENABLED, E2E_TARGET_URL, E2E_READONLY, E2E_HEADLESS, E2E_SLOWMO_MS, E2E_TRACE, credentials, plus the standard app config pass-throughs — full table in the doc). Rider debugging needs no extra machinery: tests are plain xunit, Local.runsettings carries the variables, and in integration mode both apps run inside the test process, so a breakpoint in BlazorApp/WebApi source hits during a browser click. The doc also covers the read-only mode semantics (mutating tests self-skip), an E2E_ENABLED guard so solution-wide dotnet test stays green, the Blazor Server prerender/circuit waiting gotcha, failure traces/screenshots, a dedicated CI job with browser caching, and a 4-phase implementation plan (foundation → smoke suite → CI → later types/admin/import).
…k, Car, House, Playlist, Song, VideoGame) - Removed the racy finalItems.TotalCount.Should().BeGreaterThan(initialItems.TotalCount) assertion — it's a global count over a shared tenant, and other test classes (RefreshReferenceResourceTest, TvTimeImportResourceTest, etc.) create/delete items against the same endpoints concurrently for the same fixed test user, so a sibling's create+delete cycle can straddle the window and mask this test's own +1. - Removed the now-dead initial GetAsync<PagedResult<TDto>>(...) call that only existed to capture the baseline count for that assertion. - Kept (unchanged) the assertions that actually prove creation happened and do check their result: the by-id GetAsync<TDto> right after update (updated.Should().BeEquivalentTo(created)) and the list-presence check (firstItem.Should().NotBeNull() / firstItem.Title.Should().Be(...)) — both scoped by Id, so they're immune to concurrent siblings. Left CarHistoryResourceTest and HouseHistoryResourceTest untouched — their TotalCount assertions are already scoped to a per-test unique CarId/HouseId, so no other test can share that filter value and they aren't actually racy.
|


No description provided.