test: add ClassifyContentAsyncTests — cover all 3 branches (null response, exception, type-switch)#175
Open
davidortinau wants to merge 29 commits intomainfrom
Open
test: add ClassifyContentAsyncTests — cover all 3 branches (null response, exception, type-switch)#175davidortinau wants to merge 29 commits intomainfrom
davidortinau wants to merge 29 commits intomainfrom
Conversation
- Orchestration log: DX24 vocab crash fix workflow (commit c9b1d0a) - Session log: ExposureCount NULL→0 backfill, verified on device - Decision merge: inbox → decisions.md (3 entries: lexical patch, validation strategy, directive) - Deleted: inbox files + jayne-locale-screenshots (deduped) - Wash history: append CRITICAL RULE for SQLite migration defense-in-depth (DEFAULT + idempotent backfill) - No archival needed (no entries >30d old in decisions.md) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Orchestrated multi-agent session for new generic data import feature architecture: - Zoe (Lead): Full MVP architecture covering UX flows, content detection (heuristic-first, AI fallback), data model (no new tables), parsing pipeline, AI integration, service layer, scope phasing. Placement revision: separate `/import-content` page (not tab on existing `/import`), per Captain directive. - Wash (Data Layer Scout): Surveyed existing YouTube pipeline pattern, found file import UI/service gap, confirmed no schema changes needed, identified dedup inconsistency for standardization. - River (AI Strategy): Designed 5 AI prompt tasks with confidence thresholds, heuristic fallbacks, structured DTOs via `SendPrompt<T>`, permissive grading principles. - Kaylee (UI Scout): Documented form patterns, file picker abstractions, resource lookup UI, navigation placement. Recommendations for ContentImportService, InputFile, preview table reuse. - Copilot Directive: Confirmed scope separation — Video Subscriptions (existing) separate from generic content import (new) to keep features focused and growth paths independent. Artifacts: - 5 orchestration logs (one per agent) in .squad/orchestration-log/ - Session log in .squad/log/2026-04-24T22:31-data-import-architecture-plan.md - 6 decisions merged into .squad/decisions.md (168KB total) - All agent histories updated with import-plan context Next: Implementation team begins ContentImportService + ImportContent.razor page. River engineers 3 prompt templates (Format Inference, Content Classification, Extraction). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Captain resolved 7 open questions from architecture proposal: 1. Dedup default: Skip and reuse existing (safest, matches YouTube pipeline) 2. Source attribution: Deferred to v2 3. Single-column imports: Use AI to translate on import (new mvp-single-column-translate work item) 4. Mobile clipboard paste: Not needed for MVP 5. Replace-all mode: Deferred to v2 6. Renamed YouTube page: 'Media Import' at /media-import (not 'Video Subscriptions') 7. Separate-page placement: Confirmed — /import-content independent from /media-import Updated decisions.md with final rulings appended and prior superseded mentions annotated. All prior architecture content preserved; only updated with Captain's decisions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t rename + FreeTextToVocab prompts - Add ContentImportService (Shared/Services) with locked DTO surface, full transaction-safe CommitImportAsync, scoped DI - Rename Import.razor → MediaImport.razor, route /import → /media-import (back-compat preserved on both routes) - Add FreeTextToVocab.scriban-txt + TranslateMissingNativeTerms.scriban-txt prompts and response DTOs - Standardized dedup rule: case-sensitive, whitespace-trimmed on TargetLanguageTerm - Captain's rulings 2026-04-24 applied throughout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ContentImportService.ParseContentAsync now real: CSV/TSV/pipe sniffing, JSON parse, free-text AI fallback (50KB cap), single-column AI translation - Add ImportContent.razor at /import-content — 7-step wizard (paste → preview → target → dedup → commit) with editable preview table, badge-marked AI-translated rows, new/existing resource picker, dedup mode selector - +43 localization keys (English + Korean) for import flow - Dedup audit complete: VideoImportPipelineService inconsistency documented for Captain - Cleanup: stage delete of legacy Import.razor missed by Wave 1 commit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…E script - Extract IAiService interface; dual DI registration (concrete + interface alias) keeps 16 existing consumers untouched - Migrate ContentImportService constructor to IAiService dependency - Add ContentImportServiceTests.cs — 18 unit tests, 0 failures, ~95% coverage of public API • Format detection (CSV/TSV/pipe/JSON), header rows, delimiter override • Two-column happy path, empty-row error, single-column AI translation, free-text AI fallback, 50KB cap • Whitespace-trimmed + case-sensitive dedup • CommitImportAsync: Skip / Update / ImportAll / new-resource / existing-resource paths • NotSupportedException for v2 stubs (Phrases, Transcript) - Add 15-scenario E2E script at .claude/skills/e2e-testing/references/import-content.md - 0 bugs discovered — implementation shipshape MVP COMPLETE: 11/11 todos shipped on feature/import-content-mvp. v2 backlog: phrases/transcript content types, AI auto-detect with confidence, ImportHistory + undo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Code-review findings on feature/import-content-mvp before push:
1. CommitImportAsync could create duplicate VocabularyWord rows when a
single import contained multiple rows with the same trimmed
TargetLanguageTerm. EF's FirstOrDefaultAsync queries the database and
does not see tracked-but-unsaved entities, so each row in the loop
minted a fresh word. Added an in-batch Dictionary cache keyed by
trimmed target term that:
- Skip mode reuses the in-flight word (counts as skipped)
- Update mode applies last-non-empty native term (last write wins)
- ImportAll bypasses the cache so duplicates remain intentional
Two regression tests added (Skip + ImportAll variants).
2. ImportContent.razor preview never rendered the row.IsAiTranslated
flag despite the E2E script and Captain's ruling #3 requiring a
visible badge. Added a Bootstrap bi-stars badge in the
NativeLanguageTerm cell with EN + KO localization keys
(Import_AiTranslated_Badge / _Tooltip).
Tests: 20/20 ContentImportServiceTests passing (was 18, +2 new).
UI build: 0 errors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bugs found while walking the import-content wizard end-to-end via Playwright:
1. NavigationMemoryService missing 'import-content' route
- NavMenu.razor TopItems contained the 'import-content' key, but
NavigationMemoryService.Sections array didn't have a matching entry.
- GetSectionRoute() silently returned '/' (Array.Find default tuple),
so clicking the sidebar 'Import Content' button quietly redirected
users to the dashboard with no error.
- Added ('import-content', '/import-content', ['/import-content']).
2. Paste textarea Preview button stuck disabled
- <textarea @Bind="rawText"> defaulted to the 'change' event, which
only fires on blur. Users who pasted/typed and immediately clicked
Preview saw a disabled button until they clicked outside.
- Added @Bind:event="oninput" so rawText updates on every keystroke.
3. AI translation badge never rendered
- ContentImportService correctly set ImportRow.IsAiTranslated=true
for AI-translated rows, but the editableRows projection in
ParseContent() copied every property EXCEPT IsAiTranslated, so the
bi-stars 'AI' badge rendered for nobody.
- Added IsAiTranslated to the projection.
E2E verified end-to-end against Aspire-hosted Postgres webapp:
- 5-row CSV happy path: 1 LearningResource + 5 VocabularyWord + 5 mappings
persisted in Postgres (verified via psql).
- Intra-batch dedup: 5 input rows with duplicates → 3 unique mappings,
reusing pre-existing word rows for already-known terms.
- Single-column AI: 사과/바나나/포도 → apple/banana/grape with bi-stars
'AI' badge visible on each row.
20/20 ContentImportService tests still pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add scope-discipline rule (no silent deferral of named features) - Capture 4 autonomous decisions for import v1.1 - Land Zoe's v1.1 architecture spec - Correct Decision #1: use existing LexicalUnitType enum - Status: BLOCKED on captain-confirm-scope Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ipt/auto-detect branches Migration SetDefaultLexicalUnitType: - Heuristic backfill: Unknown→Word (no space) or Phrase (has space) - Dual-provider: Postgres POSITION, SQLite INSTR - Down() is safe no-op (no data loss) ContentImportService v1.1: - Phrase branch: harvests Words + Phrases via FreeTextToVocab prompt - Transcript branch: stores full text + word-biased extraction - Auto-detect branch: three-tier confidence gate (>=0.85/0.70/below) - Classification runs BEFORE any DB persistence (Captain directive) - Checkbox harvest model: HarvestTranscript/Phrases/Words booleans - ImportRow gains LexicalUnitType for per-row classification - Zero-vocab: persist resource + warning (no silent success) - Transcript >30KB: reject with clear error (chunking in v1.2) UI: adapted DetectContentType → ClassifyContentAsync (async) Blockers on River: ClassifyImportContent + ExtractPhrasesFromContent prompts not yet landed — using bridge implementations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Archive decisions.md (229KB), merge 10 inbox decisions (River prompts, Wash backend, Kaylee UI, Jayne tests + phrase gap, 5 Captain confirms), write 4 orchestration logs, session log, update 5 agent histories. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cisions merge This commit logs the complete v1.1 Data Import cycle from initial DO-NOT-SHIP verdict through bug fixes by Simon (backend) and Kaylee (frontend DTO mapping), retest by Jayne, and final full sweep confirming 10/10 scenarios PASS with SHIP verdict. Artifacts: - Orchestration logs: 4 agents × 4 spans (simon, jayne-retest, kaylee-mapping-fix, jayne-full-sweep) - Session log: 2026-04-26T23:23:07Z-v11-import-ship.md (cycle summary from rejection to SHIP) - Decisions merge: All 5 v1.1 inbox files appended to .squad/decisions.md (inbox cleaned) - Agent history updates: simon, wash (lockout), kaylee (DTO discipline), jayne (dual-layer verification), river (prompts clean) Key learnings captured: - Simon: UserProfileId scoping, transcript DTO carry-through, LLM mapping discipline - Kaylee: DTO mapping completeness audit pattern - Jayne: Frontend DTO bugs masquerade as backend bugs; dual-layer verification required - River: Prompt engineering discipline validated; C# layer had the bugs - Wash: Lockout scoped to 3 specific bugs; resolution delivered; learnings documented for future Bug fixes verified with DB-level evidence across 10 regression scenarios. All P1/P0 bugs confirmed fixed; zero regressions; clean Aspire logs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…+ Phrases/Transcript/Auto Three production-blocking bugs from e2e: BUG-1 (P0, UserProfileId leak): ContentImportService bypassed LearningResourceRepository.SaveAsync, writing raw to DbContext and leaving UserProfileId null. Inject IPreferencesService and source ActiveUserId on the service path so new resources are owned by the active profile (mirrors existing repository pattern). BUG-2 (P1, SourceText drop): Commit path discarded preview source text when TranscriptText was unset. Add SourceText to ContentImportPreview, fall back commit.TranscriptText ?? commit.Preview.SourceText, and pass the field through the Razor preview→commit DTO mapping. BUG-3 (P1, LexicalUnitType lost): ParseFreeTextContentAsync never mapped item.LexicalUnitType from the AI response — every row defaulted to Word silently, including obvious phrases. Wire explicit mapping in all three row-creation paths (free-text, transcript, CSV) and add ResolveLexicalUnitType heuristic fallback (multi-word term → Phrase). ImportContent.razor also re-mapped LexicalUnitType in ImportRow→preview. Adds the v1.1 content-type prompts: - ClassifyImportContent.scriban-txt — auto-detect classifier - ExtractVocabularyFromPhrases.scriban-txt — phrase + constituent words - ExtractVocabularyFromTranscript.scriban-txt — word-bias rework Verification: 10/10 e2e scenarios PASS on Mac Catalyst (David, ko-KR). 0 cross-user UserProfileId leaks. SourceText round-trips at 441/219 chars. LexicalUnitType: 0 Unknown, 213 Word, 6 Phrase. Aspire logs clean. Known follow-ups (not blocking ship): - BUG-4 (P2): AI confidence calibration never returns <85% - New-resource save isn't wrapped in a transaction (low-prob orphan) - Korean localization gap on harvest checkbox section - Silent title validation - Classification / RequiresUserConfirmation DTO fields unconsumed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add LexicalUnitType filter dropdown (All/Word/Phrase/Sentence) to both desktop filter row and mobile offcanvas panel - Rename 'Add Word' button to 'Add' (new Vocabulary_Add localization key) - Add type filter case in ApplyFilters (client-side, no new queries) - Add type entries in filter badge/icon helpers - Add localization keys for en + ko Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Part A: ExtractVocabularyFromPhrases.scriban-txt - Root cause: service wires to FreeTextToVocab instead of this prompt (Wash fix) - Added harvest_phrases/harvest_words Scriban conditionals - Added pipe-delimited input handling - Added CRITICAL rule: each input line MUST produce a Phrase entry - Added Captain's exact Korean|English few-shot example Part B: ExtractVocabularyFromSentences.scriban-txt (NEW) - Sentence as first-class content type (LexicalUnitType.Sentence) - Three harvest flags: harvest_sentences, harvest_phrases, harvest_words - Sentences preserve original text verbatim (no dictionary normalization) - Three few-shot examples with Korean|English Part C: ClassifyImportContent.scriban-txt - Added Sentences as fourth classification type - Heuristic: sentence-completeness test (subject+predicate+punctuation) - Sentences vs Phrases: full utterances vs sub-sentence fragments - Added borderline-case guidance and two new few-shot examples Part D: Coordination doc for Wash in decisions inbox Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause: Phrases branch called ParseFreeTextContentAsync (generic FreeTextToVocab prompt) which decomposed input into individual words, silently dropping phrase/sentence entries. River's dedicated ExtractVocabularyFromPhrases.scriban-txt was deployed but never wired in. Fix: - Rewrote Phrases/Sentences branch with two-step pipeline: 1. Parse delimited lines for primary phrase/sentence entries 2. Run AI phrase extraction for constituent words 3. Combine with dedup, filter by harvest flags - Added ContentType.Sentences enum value - Added HarvestSentences boolean on ContentImportRequest/Commit DTOs - Refined ResolveLexicalUnitType: terminal punctuation -> Sentence, whitespace only -> Phrase (replaces blanket space->Phrase heuristic) - Updated classifier prompt to recognize Sentences as fourth type - Updated stale test (Phrases no longer throws NotSupportedException) - No schema migration needed (LexicalUnitType.Sentence already exists) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round 1 complete — three-agent convergent diagnosis of phrase-save bug root cause.
ROOT CAUSE: ContentImportService.cs line 192 routed Phrases branch to generic
ParseFreeTextContentAsync() instead of River's dedicated ExtractVocabularyFromPhrases
prompt (which was written but never wired in — TODO comment at line 191).
FIXES:
- Wash: Rewrote Phrases as 2-step pipeline (parse delimited → preserve phrase entries
→ AI harvest constituent words → dedup). Added ContentType.Sentences + HarvestSentences
flag. Refined ResolveLexicalUnitType heuristic (terminal punctuation → Sentence).
Build green, 610 tests passing.
- River: Fixed ExtractVocabularyFromPhrases prompt (harvest flags, pipe handling).
Created NEW ExtractVocabularyFromSentences prompt. Updated ClassifyImportContent to
four-class output. Locked JSON response shape (identical across all 3 prompts).
- Kaylee: Added Type filter dropdown to Vocabulary page (Round 1). Round 2 pending:
Sentences button + harvest checkbox in import UI.
- Jayne: Reproduced bug (3 Korean|English in → 8 words, 0 phrases). Created 7-section
test plan for Round 2 validation.
FILES:
- .squad/orchestration-log/2026-04-27T0330Z-{wash,river,kaylee,jayne}.md — per-agent logs
- .squad/log/2026-04-27T0330Z-v12-import-bug-fix.md — session summary
- .squad/decisions.md — merged 4 inbox decisions (delete inbox files after)
- .squad/agents/{wash,river,kaylee,jayne}/history.md — team convergence notes
No schema migration needed (LexicalUnitType.Sentence already exists).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nto UI - Add Sentences option to content type dropdown (between Phrases and Transcript) - Add Harvest Sentences checkbox (order: Sentences > Phrases > Words) - Add Sentences button to auto-detect type chooser and override panel - Wire HarvestSentences into ContentImportRequest (preview) and ContentImportCommit - Extend harvest validation to include harvestSentences in at-least-one check - Add Sentences case to FormatContentType display helper - Defaults: Sentences type sets Sentences=true, Words=true, Phrases=false - Reset harvestSentences in StartNewImport Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
S2 (primary): ParseContentAsync always passed LexicalUnitType.Phrase as the hint to ResolveLexicalUnitType regardless of content type. With Sentences harvest defaults (harvestPhrases=false), primary rows were classified Phrase then filtered out — only AI Word rows survived. Fix: pass LexicalUnitType.Sentence hint when effectiveContentType is Sentences. Also reorder ResolveLexicalUnitType to check single-token BEFORE trusting the Phrase/Sentence hint (single tokens stay Word per Captain's directive). S1 (secondary): AI extraction always used the Phrases prompt. Now branches to ExtractVocabularyFromSentencesAsync which loads River's ExtractVocabularyFromSentences.scriban-txt and passes harvest flags. 4 new tests verify: sentences with punctuation, sentences without punctuation, single-token stays Word, phrases regression guard. All 24 ContentImportService tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…+ decision merge + history updates Rounds 3-4 of v1.2 import bug fix cycle completed: - Round 3 (Jayne): E2E validation identified Sentences producing 0 type=3 rows - Root cause investigation (Wash): Two bugs found (content-type-unaware hint + unwired Sentences AI prompt) - Round 4 (Jayne): Re-verification confirmed fix working (Test 1 regression guard PASS, Test 2 fix PASS) Files: - Added orchestration logs: jayne-r3, wash-r3, jayne-r4 - Added session log: v12-sentences-branch-fix - Merged decision inbox into decisions.md (wash-sentences-branch-fix.md) - Deleted inbox file - Updated agent history (Wash + Jayne) with R3-R4 entries DB verification: Word +7, Phrase +5, Sentence +4 (was 0, now working) Commit under test: 3c7a4cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add ImportItemStatus enum (Created/Updated/Skipped/Failed) and ContentImportItemResult class with VocabularyWordId, Lemma, NativeLanguageTerm, Type, Status, and curated Reason fields. Every branch in CommitImportAsync that increments an aggregate count now also appends a detail item to the new Items list. Failed rows log via _logger.LogError with structured fields for Aspire retrieval. Invariant: Items.Count == CreatedCount + UpdatedCount + SkippedCount + FailedCount 8 new unit tests (32 total ContentImportService tests passing). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add per-row results table below the 4-card summary showing Lemma,
Native Term, Type badge, Status pill, and Reason for each imported row
- Add filter pills (All/Created/Updated/Skipped/Failed) for client-side
filtering of the results table
- Clickable rows link to /vocabulary/edit/{id} for Created/Updated/Skipped
items; Failed rows are not clickable and show curated failure reason
- Mobile responsive: stacked card layout on narrow viewports, table on desktop
- Add IImportResultStore service (Singleton, 30-min TTL) for state
preservation across back-navigation via URL query param ?completed={guid}
- Register ImportResultStore in BlazorUIServiceExtensions.cs
- Add 8 new localization keys (en + ko) for table headers, filter pills,
and view link text
- Add blazor-nav-state-preservation skill to .squad/skills/
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Scribe: orchestration logs for Wash, Kaylee, Jayne - orchestration-log/2026-04-27T14:53:00Z-wash.md — backend DTOs + tests - orchestration-log/2026-04-27T14:53:00Z-kaylee.md — UI redesign + state mgmt - orchestration-log/2026-04-27T14:53:00Z-jayne.md — E2E validation (7/7 PASS) Session log: - log/2026-04-27T14:53:00Z-v13-import-detail.md — cycle summary + ship gate Decision inbox merged & archived: - Merged: wash-import-item-result.md (ContentImportResult DTO design) - Merged: kaylee-import-result-store.md (singleton store + TTL strategy) - Merged: jayne-v13-import-detail-verdict.md (E2E test results) Cross-agent updates: - wash/history.md: appended Jayne verdct note - kaylee/history.md: appended Jayne verdict note Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add IsDuplicate (bool) and DuplicateReason (string?) to ImportRow DTO. New EnrichPreviewWithDuplicateInfoAsync method uses a single batched DB query to flag rows that already exist in VocabularyWord table, plus intra-batch duplicate detection for repeated terms within the same preview. Extract NormalizeTargetTerm() as the shared matching predicate used by both preview enrichment and CommitImportAsync — single source of truth for the trimmed, case-sensitive ordinal comparison. Reason keys: AlreadyInVocabulary, DuplicateWithinBatch. 4 new tests (36 total ContentImportService tests passing): - Exact duplicate flagged correctly - Near-miss (different lemma) not flagged - Batch query structure (no N+1) - Round-trip invariant: Preview IsDuplicate matches Commit Skip/Create Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Documented the style normalization patterns applied to ImportContent.razor:
- Badge type system: bg-{color} bg-opacity-10 text-{color}
- Clickable elements: role="button" not inline cursor
- No bespoke hex colors in Razor markup
- Duplicate badge column patterns (coordinated with Wash)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…azor Wash's backend enrichment method was defined and tested but never called from the Razor code-behind. Preview rows were always showing IsDuplicate=false. Added the missing call between ParseContentAsync and row mapping, which is the integration point Wash's decision doc specified. Found by Jayne during E2E validation of commit 5d98a27. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Merged wash-preview-duplicate-flag.md (EnrichPreviewWithDuplicateInfoAsync contract) - Merged kaylee-import-style-cleanup.md (style audit + badge pattern) - Captured integration gap learning in Jayne's history - Deleted merged inbox files - Updated Scribe history with v1.4 cycle summary Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Vocabulary / 0.5f confidence
Vocabulary / 0.3f confidence with error signals
- 4-arm type-switch (vocabulary, phrases, sentences, transcript)
Vocabulary fallback
Also adds [InternalsVisibleTo("SentenceStudio.UnitTests")] via
src/SentenceStudio.Shared/AssemblyInfo.cs to expose the internal
ContentClassificationAiResponse DTO to the test project.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
ClassifyContentAsyncTests.cs— 6 xUnit tests that directly exercise every code path inContentImportService.ClassifyContentAsync(ContentImportService.cs:870), which was previously untested. Existing tests mock away classification by pre-settingContentTypeon the request, so the mapping and fallback logic had zero coverage.Also adds
[InternalsVisibleTo("SentenceStudio.UnitTests")]via a newAssemblyInfo.csin the Shared project to expose theinternal ContentClassificationAiResponseDTO to the test project.Why
ClassifyContentAsyncis the hot auto-detect path on thefeature/import-contentbranch. Without direct tests, regressions in any of its three branches (null AI response, AI exception, type-switch mapping) would only surface as silent wrong behaviour in integration.Tests added
ClassifyContent_NullAiResponse_DefaultsToVocabularyClassifyContent_AiException_DefaultsWithLowConfidence"error"signalClassifyContent_KnownType_MapsCorrectly(Theory ×4)vocabulary,phrases,sentences,transcriptarmsClassifyContent_UnknownType_FallsBackToVocabulary_arm → VocabularyVerification
489 pre-existing tests all still pass.