@@ -180,40 +180,81 @@ builder.AiChatMiddleware()
180180
181181---
182182
183- ## Phase 2: IAiEmbeddingService Implementation
183+ ## Phase 2: IAiEmbeddingService Implementation ✅ COMPLETED
184+
185+ ### Implementation Status: COMPLETE (2025-11-26)
186+
187+ ** Completed Changes:**
188+ - ✅ ` IAiEmbeddingService.cs ` - Created interface with 5 methods for single/batch embedding generation and direct generator access
189+ - ✅ ` AiEmbeddingService.cs ` - Created internal implementation following ` AiChatService ` pattern
190+ - ✅ ` UmbracoBuilderExtensions.cs ` - Registered ` IAiEmbeddingService ` in DI container
191+ - ✅ ` FakeEmbeddingCapability.cs ` - Enhanced ` FakeEmbeddingGenerator ` to track received values/options for testing
192+ - ✅ ` AiEmbeddingServiceTests.cs ` - Created 16 comprehensive unit tests
193+
194+ ** Branch:** ` feature/phase-2-embedding-service `
184195
185196### New Files
186197
187198** ` src/Umbraco.Ai.Core/Services/IAiEmbeddingService.cs ` **
188199``` csharp
189200public interface IAiEmbeddingService
190201{
202+ // Single value - default profile
203+ Task <Embedding <float >> GenerateEmbeddingAsync (
204+ string value ,
205+ EmbeddingGenerationOptions ? options = null ,
206+ CancellationToken cancellationToken = default );
207+
208+ // Single value - specific profile
209+ Task <Embedding <float >> GenerateEmbeddingAsync (
210+ Guid profileId ,
211+ string value ,
212+ EmbeddingGenerationOptions ? options = null ,
213+ CancellationToken cancellationToken = default );
214+
215+ // Multiple values - default profile
191216 Task <GeneratedEmbeddings <Embedding <float >>> GenerateEmbeddingsAsync (
192217 IEnumerable <string > values ,
193218 EmbeddingGenerationOptions ? options = null ,
194219 CancellationToken cancellationToken = default );
195220
221+ // Multiple values - specific profile
196222 Task <GeneratedEmbeddings <Embedding <float >>> GenerateEmbeddingsAsync (
197223 Guid profileId ,
198224 IEnumerable <string > values ,
199225 EmbeddingGenerationOptions ? options = null ,
200226 CancellationToken cancellationToken = default );
201227
202- Task <Embedding <float >> GenerateEmbeddingAsync (string value , ...);
203-
228+ // Direct generator access for advanced scenarios
204229 Task <IEmbeddingGenerator <string , Embedding <float >>> GetEmbeddingGeneratorAsync (
205- Guid ? profileId = null , CancellationToken cancellationToken = default );
230+ Guid ? profileId = null ,
231+ CancellationToken cancellationToken = default );
206232}
207233```
208234
209235** ` src/Umbraco.Ai.Core/Services/AiEmbeddingService.cs ` **
210236- Inject ` IAiEmbeddingGeneratorFactory ` , ` IAiProfileService ` , ` IOptionsMonitor<AiOptions> `
211- - Resolve default profile via ` AiOptions.DefaultEmbeddingProfileAlias `
237+ - Resolve default profile via ` IAiProfileService.GetDefaultProfileAsync(AiCapability.Embedding, ...) `
212238- Delegate to factory for generator creation
239+ - Options merging: caller options override profile defaults
213240
214241### Modified Files
215242
216243- ` src/Umbraco.Ai.Core/Configuration/UmbracoBuilderExtensions.cs ` - Register ` IAiEmbeddingService `
244+ - ` tests/Umbraco.Ai.Tests.Common/Fakes/FakeEmbeddingCapability.cs ` - Enhanced ` FakeEmbeddingGenerator ` with ` ReceivedValues ` and ` ReceivedOptions ` tracking
245+
246+ ### Tests Implemented
247+
248+ ** Unit Tests (` tests/Umbraco.Ai.Tests.Unit/Services/AiEmbeddingServiceTests.cs ` ):**
249+
250+ 16 tests covering:
251+ - ✅ Default embedding profile resolution
252+ - ✅ Named profile resolution by ID
253+ - ✅ Error handling: profile not found (` InvalidOperationException ` )
254+ - ✅ Error handling: profile has wrong capability (Chat instead of Embedding)
255+ - ✅ Options merging when caller provides custom ` EmbeddingGenerationOptions `
256+ - ✅ Multiple values returning embeddings for each value
257+ - ✅ Direct generator access via ` GetEmbeddingGeneratorAsync `
217258
218259---
219260
@@ -311,6 +352,48 @@ src/Umbraco.Ai.Web/Api/Management/
311352| Chat | POST | ` /chat/complete ` | Chat completion (non-streaming) |
312353| Chat | POST | ` /chat/stream ` | Chat completion with SSE streaming |
313354
355+ ### Testing Requirements
356+
357+ ** Unit Tests (` tests/Umbraco.Ai.Tests.Unit/Api/ ` ):**
358+
359+ For each controller, test the critical request/response mapping and validation:
360+
361+ ** Connection Controllers:**
362+ - ` AllConnectionController ` - Returns paginated list
363+ - ` ByIdConnectionController ` - Returns 404 when not found
364+ - ` CreateConnectionController ` - Validates required fields, returns created entity with ID
365+ - ` UpdateConnectionController ` - Returns 404 when not found
366+ - ` DeleteConnectionController ` - Returns 404 when not found, handles in-use connections
367+ - ` TestConnectionController ` - Returns test result with success/failure status
368+
369+ ** Profile Controllers:**
370+ - ` AllProfileController ` - Filters by capability query param
371+ - ` ByIdProfileController ` - Returns 404 when not found
372+ - ` ByAliasProfileController ` - Returns 404 when alias not found
373+ - ` CreateProfileController ` - Validates required fields, alias uniqueness
374+ - ` UpdateProfileController ` - Returns 404 when not found
375+ - ` DeleteProfileController ` - Returns 404 when not found
376+
377+ ** Provider Controllers:**
378+ - ` AllProviderController ` - Returns all registered providers
379+ - ` ByIdProviderController ` - Returns settings schema, 404 when not found
380+ - ` ModelsByProviderController ` - Returns available models for provider
381+
382+ ** Embedding Controller:**
383+ - ` GenerateEmbeddingController ` - Validates input, returns embeddings array
384+
385+ ** Chat Controllers:**
386+ - ` CompleteChatController ` - Validates messages array, returns response
387+ - ` StreamChatController ` - Returns SSE stream (integration test preferred)
388+
389+ ** Integration Tests (` tests/Umbraco.Ai.Tests.Integration/Api/ ` ):**
390+
391+ Create integration tests for full HTTP request/response cycles:
392+ - Connection CRUD workflow (create → read → update → delete)
393+ - Profile CRUD workflow with connection dependency
394+ - Test connection with fake provider
395+ - Generate embeddings with fake provider
396+
314397---
315398
316399## Phase 4: EF Core Database Persistence
@@ -536,6 +619,84 @@ The `MigrationsAssembly()` call in each provider's setup ensures EF Core looks f
536619
537620Migrations auto-apply on startup via ` RunAiMigrationNotificationHandler ` .
538621
622+ ### Testing Requirements
623+
624+ ** Unit Tests (` tests/Umbraco.Ai.Tests.Unit/Repositories/ ` ):**
625+
626+ Test repository methods with in-memory SQLite:
627+
628+ ** EfCoreAiConnectionRepository:**
629+ - ` GetAsync ` - Returns null when not found
630+ - ` GetAllAsync ` - Returns empty list when no data
631+ - ` SaveAsync ` - Creates new entity (insert)
632+ - ` SaveAsync ` - Updates existing entity (upsert)
633+ - ` DeleteAsync ` - Removes entity, returns true
634+ - ` DeleteAsync ` - Returns false when not found
635+ - Settings JSON serialization/deserialization
636+
637+ ** EfCoreAiProfileRepository:**
638+ - ` GetByIdAsync ` - Returns null when not found
639+ - ` GetByAliasAsync ` - Case-insensitive alias lookup
640+ - ` GetAllAsync ` - Filters by capability
641+ - ` SaveAsync ` - Enforces unique alias constraint
642+ - ` DeleteAsync ` - Handles foreign key constraint (connection reference)
643+ - Tags JSON serialization/deserialization
644+
645+ ** Migration Tests:**
646+ - Verify migrations apply cleanly to empty database
647+ - Verify schema matches entity configuration
648+
649+ ** Test Fixture (` tests/Umbraco.Ai.Tests.Common/Fixtures/EfCoreTestFixture.cs ` ):**
650+
651+ ``` csharp
652+ public class EfCoreTestFixture : IDisposable
653+ {
654+ private readonly SqliteConnection _connection ;
655+
656+ public UmbracoAiDbContext CreateContext ()
657+ {
658+ var options = new DbContextOptionsBuilder <UmbracoAiDbContext >()
659+ .UseSqlite (_connection )
660+ .Options ;
661+ return new UmbracoAiDbContext (options );
662+ }
663+
664+ public EfCoreTestFixture ()
665+ {
666+ _connection = new SqliteConnection (" DataSource=:memory:" );
667+ _connection .Open ();
668+
669+ using var context = CreateContext ();
670+ context .Database .EnsureCreated ();
671+ }
672+
673+ public void Dispose () => _connection .Dispose ();
674+ }
675+ ```
676+
677+ Usage in tests:
678+ ``` csharp
679+ public class EfCoreAiConnectionRepositoryTests : IClassFixture <EfCoreTestFixture >
680+ {
681+ private readonly EfCoreTestFixture _fixture ;
682+
683+ public EfCoreAiConnectionRepositoryTests (EfCoreTestFixture fixture )
684+ => _fixture = fixture ;
685+
686+ [Fact ]
687+ public async Task GetAsync_WhenNotFound_ReturnsNull ()
688+ {
689+ await using var context = _fixture .CreateContext ();
690+ var scopeProvider = CreateScopeProvider (context );
691+ var repository = new EfCoreAiConnectionRepository (scopeProvider );
692+
693+ var result = await repository .GetAsync (Guid .NewGuid ());
694+
695+ result .ShouldBeNull ();
696+ }
697+ }
698+ ```
699+
539700---
540701
541702## Phase 5: Frontend UI Implementation
@@ -728,6 +889,22 @@ export class AiConnectionWorkspaceContext
728889- Dynamically renders ` <umb-property> ` for each setting based on ` EditorUiAlias `
729890- Emits change events with updated values
730891
892+ ### Testing Requirements
893+
894+ ** No automated tests required for v1.**
895+
896+ Rationale: Frontend tests (Playwright/WebdriverIO) add significant complexity and maintenance overhead. For v1, rely on manual QA testing of the UI. Consider adding E2E tests in v2 once the UI patterns stabilize.
897+
898+ ** Manual Test Checklist:**
899+ - Connections collection view loads and displays data
900+ - Connection create/edit workspace saves correctly
901+ - Connection delete shows confirmation and removes entity
902+ - Profiles collection view loads with capability filter
903+ - Profile create/edit workspace validates required fields
904+ - Profile delete handles connection dependency warning
905+ - Dynamic settings form renders based on provider schema
906+ - Model picker filters by provider and capability
907+
731908---
732909
733910## Implementation Order
0 commit comments