Initial commit: JDE Scoping Tool migration project

Set up repository with legacy .NET Framework 4.8 source (OLD/),
new .NET 10 Blazor solution (NEW/), OpenSpec specifications,
documentation, and project configuration.
This commit is contained in:
Joseph Doherty
2026-01-02 07:43:29 -05:00
commit 26ff8d9b4f
1761 changed files with 596509 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,91 @@
# Implement Web API
## Summary
Implement the REST API layer and real-time SignalR hub for the JDE Scoping Tool, providing HTTP endpoints for search management, lookup operations, file upload/download, and authentication. This phase creates the web-facing interface that connects the Blazor WebAssembly client to the backend search processing and data access layers.
## Scope
### In Scope
- `AuthController` with login, logout, and current user endpoints
- `SearchController` with CRUD operations and result downloads
- `LookupController` with autocomplete APIs for items, profit centers, work centers, and operators
- `FileController` with Excel upload/download for bulk data import
- `StatusHub` SignalR hub for real-time status and search updates
- `IAuthService` interface with LDAP and fake authentication implementations
- `LdapAuthService` using `System.DirectoryServices.Protocols` (cross-platform)
- `FakeAuthService` for development mode authentication bypass
- `LdapOptions` and `AuthOptions` configuration classes
- `UserInfo` model (renamed from legacy `LDAPEntry`)
- API model DTOs (`LoginRequest`, `AuthResult`, `FileUploadResult<T>`, etc.)
- Cookie-based session management with ASP.NET Core authentication
- Service registration extension methods (`AddWebApi`, `AddAuthentication`)
- OpenAPI/Swagger documentation
- Unit tests with xUnit, Shouldly, and NSubstitute
### Out of Scope
- Blazor WebAssembly client implementation (Phase 9)
- Background worker service (Phase 5: implement-data-sync)
- Search execution logic (Phase 6: implement-search-processing)
- Excel export generation (Phase 7: implement-excel-export)
- Database schema changes (Phase 1: migrate-database-schema)
- Rate limiting and advanced security (future enhancement)
## Motivation
The Web API layer is the bridge between the Blazor WebAssembly client and the backend services. This phase delivers:
- **REST API Endpoints**: Standard HTTP APIs for search, lookup, and file operations
- **Real-Time Updates**: SignalR hub for live status updates during search processing
- **Cross-Platform Authentication**: LDAP authentication using `System.DirectoryServices.Protocols` (not the Windows-only `System.DirectoryServices`)
- **Development Mode Support**: Fake authentication for local development without LDAP server
- **OpenAPI Documentation**: Auto-generated API documentation for Blazor client development
## Acceptance Criteria
1. `AuthController` implements login, logout, and current user endpoints
2. `SearchController` implements all CRUD operations with proper authorization
3. `LookupController` implements autocomplete APIs without authorization (public access)
4. `FileController` implements Excel upload/download with caching
5. `StatusHub` broadcasts status and search updates to all connected clients
6. `LdapAuthService` authenticates against LDAP with group membership verification
7. `FakeAuthService` accepts any credentials when `AuthOptions.UseFakeAuth = true`
8. Cookie authentication configured with proper timeout and no redirect on 401
9. All protected endpoints return HTTP 401 (not redirect) for Blazor WASM compatibility
10. SignalR hub maps to `/hubs/status` endpoint
11. OpenAPI documentation generated via Swagger
12. All services registered via `AddWebApi()` extension method
13. Unit tests achieve >80% code coverage for controllers and services
14. `openspec validate implement-web-api --strict` passes
## Dependencies
| Phase | Dependency | Type |
|-------|------------|------|
| Phase 4: implement-data-access | `ILotFinderRepository` for lookups and search storage | Required |
| Phase 5: implement-data-sync | Worker service publishes status updates (soft dependency) | Soft |
| Phase 6: implement-search-processing | Search execution produces results | Required |
| Phase 7: implement-excel-export | `IExcelExportService` for file downloads | Required |
**Note:** Controllers can be implemented with interface dependencies, allowing parallel development with mock implementations for testing.
## Risks
| Risk | Likelihood | Impact | Mitigation |
|------|------------|--------|------------|
| LDAP connectivity issues | Medium | High | Implement `FakeAuthService` for development; add connection retry logic |
| `System.DirectoryServices.Protocols` complexity | Medium | Medium | Follow Microsoft documentation; create comprehensive LDAP integration tests |
| SignalR connection management | Low | Medium | Use ASP.NET Core SignalR defaults; implement client reconnection in Blazor |
| Cookie authentication with Blazor WASM | Low | Medium | Configure `SuppressAuthenticationChallengeOnUnauthorized`; test cross-origin scenarios |
| File upload size limits | Low | Low | Configure `IFormFile` limits in `Program.cs`; document limits |
| Memory cache expiration for file downloads | Low | Low | Use 1-minute expiration matching legacy; remove after download |
## Related Specs
- `web-api-auth/spec.md` - Base specification for Web API and authentication
- `domain-models/spec.md` - Domain entities used by controllers
- `data-access/spec.md` - Repository interfaces for data operations
- `search-processing/spec.md` - Search processing service interfaces
- `excel-export/spec.md` - Excel export service for result downloads
@@ -0,0 +1,194 @@
# Web API and Authentication - Migration Deltas
## Purpose
This document specifies requirements ADDED or MODIFIED for the .NET 10 migration of the Web API and authentication layer. These requirements extend the base specification at `openspec/specs/web-api-auth/spec.md`.
## ADDED Requirements
### Requirement: Cross-Platform LDAP Support
The system SHALL use `System.DirectoryServices.Protocols` for LDAP authentication to ensure cross-platform compatibility.
#### Business Rules
- The system SHALL NOT use `System.DirectoryServices.DirectoryEntry` or `System.DirectoryServices.DirectorySearcher` (Windows-only APIs)
- The system SHALL use `LdapConnection` from `System.DirectoryServices.Protocols`
- The system SHALL use asynchronous patterns with `Task.Run()` for LDAP operations (the LDAP API is synchronous)
- The system SHALL dispose `LdapConnection` after each operation
#### Scenario: LDAP authentication on Linux
- **WHEN** the application runs on Linux with LDAP configuration
- **THEN** the system authenticates successfully using `System.DirectoryServices.Protocols`
### Requirement: ClosedXML for Excel Operations
The system SHALL use ClosedXML library for Excel file parsing in the File API.
#### Business Rules
- The system SHALL NOT use EPPlus (commercial license required in v7+)
- The system SHALL use `XLWorkbook` from ClosedXML for reading uploaded Excel files
- The system SHALL use 1-indexed row/column access matching ClosedXML conventions
- The system SHALL handle empty worksheets gracefully using `LastRowUsed()?.RowNumber()`
#### Scenario: Parse Excel upload with ClosedXML
- **WHEN** a user uploads an Excel file to any file upload endpoint
- **THEN** the system parses the file using ClosedXML and returns matching data
### Requirement: OpenAPI Documentation
The system SHALL provide OpenAPI/Swagger documentation for all API endpoints.
#### Business Rules
- The system SHALL use Swashbuckle.AspNetCore for OpenAPI generation
- The system SHALL expose Swagger UI at `/swagger` in development mode
- The system SHALL document all endpoints with correct HTTP methods and response types
- The system SHALL document authentication requirements for protected endpoints
#### Scenario: Developer accesses API documentation
- **WHEN** a developer navigates to `/swagger` in development mode
- **THEN** the system displays interactive API documentation for all endpoints
### Requirement: JSON Serialization with System.Text.Json
The system SHALL use System.Text.Json for all JSON serialization.
#### Business Rules
- The system SHALL NOT use Newtonsoft.Json (legacy)
- The system SHALL use `JsonStringEnumConverter` for enum serialization
- The system SHALL configure JSON options via `AddControllers().AddJsonOptions()`
- The system SHALL return JSON responses (not redirects) for all API errors
#### Scenario: Enum serialization in API response
- **WHEN** an API endpoint returns a model with an enum property
- **THEN** the system serializes the enum as a string (e.g., "Submitted" not 1)
### Requirement: IHubContext Dependency Injection
The system SHALL use `IHubContext<StatusHub>` for publishing SignalR updates from outside the hub.
#### Business Rules
- The system SHALL NOT use static `GlobalHost.ConnectionManager` pattern (legacy)
- The system SHALL inject `IHubContext<StatusHub>` into controllers and services
- The system SHALL use `Clients.All.SendAsync()` for broadcasting
- The system SHALL handle SignalR publish failures gracefully (log and continue)
#### Scenario: Controller publishes search update
- **WHEN** SearchController creates a new search
- **THEN** the system uses injected `IHubContext<StatusHub>` to broadcast the update
### Requirement: Async-First Pattern
The system SHALL use async/await patterns for all I/O operations.
#### Business Rules
- All controller actions SHALL be async with `CancellationToken` parameter
- All service methods SHALL be async with `CancellationToken` parameter
- The system SHALL use `IAsyncEnumerable` for streaming scenarios where applicable
- The system SHALL propagate `CancellationToken` to all downstream async calls
#### Scenario: Cancellation during long-running operation
- **WHEN** a client cancels a request during LDAP authentication
- **THEN** the system throws `OperationCanceledException` and stops the operation
### Requirement: Structured Logging
The system SHALL use `ILogger<T>` for structured logging.
#### Business Rules
- The system SHALL NOT use NLog directly (legacy)
- The system SHALL inject `ILogger<T>` into all controllers and services
- The system SHALL use structured logging with named parameters: `_logger.LogWarning("Failed to authenticate {Username}", username)`
- The system SHALL log at appropriate levels (Information, Warning, Error)
#### Scenario: Failed LDAP authentication logged
- **WHEN** LDAP authentication fails
- **THEN** the system logs a warning with structured username and error details
### Requirement: Options Pattern Configuration
The system SHALL use `IOptions<T>` for strongly-typed configuration.
#### Business Rules
- The system SHALL NOT use `ConfigurationManager` or `WebConfigurationManager` (legacy)
- The system SHALL bind `LdapOptions` from `Ldap` configuration section
- The system SHALL bind `AuthOptions` from `Auth` configuration section
- The system SHALL inject `IOptions<T>` or `IOptionsSnapshot<T>` as needed
#### Scenario: Configuration changes without restart
- **WHEN** configuration values change in appsettings.json
- **THEN** the system can use `IOptionsSnapshot<T>` to read updated values
## MODIFIED Requirements
### Requirement: Cookie Authentication Events (Modified)
The system SHALL suppress cookie authentication redirects and return HTTP status codes for Blazor WASM compatibility.
#### Modified Business Rules
- The system SHALL configure `OnRedirectToLogin` to return HTTP 401 instead of redirect
- The system SHALL configure `OnRedirectToAccessDenied` to return HTTP 403 instead of redirect
- The system SHALL return JSON error responses (not HTML) for authentication failures
#### Scenario: Unauthenticated API request from Blazor
- **WHEN** an unauthenticated Blazor WASM client requests a protected endpoint
- **THEN** the system returns HTTP 401 with JSON error body (no redirect)
### Requirement: SignalR Hub Endpoint Path (Modified)
The system SHALL map the StatusHub to the `/hubs/status` endpoint path.
#### Modified Business Rules
- The system SHALL map StatusHub to `/hubs/status` endpoint
- The system SHALL configure SignalR with default settings (no custom protocols)
#### Scenario: Client connects to SignalR hub
- **WHEN** a Blazor WASM client connects to SignalR
- **THEN** the system accepts connections at `/hubs/status`
### Requirement: Dependency Injected Memory Cache (Modified)
The system SHALL use DI-injected `IMemoryCache` for file template caching.
#### Modified Business Rules
- The system SHALL inject `IMemoryCache` via constructor (not `MemoryCache.Default`)
- The system SHALL use `MemoryCacheEntryOptions` with `AbsoluteExpirationRelativeToNow`
- The system SHALL use `TryGetValue<T>` pattern for type-safe cache retrieval
#### Scenario: File template cached with DI cache
- **WHEN** a file template is generated
- **THEN** the system stores it in the injected `IMemoryCache` with 1-minute expiration
## Migration Notes
| Legacy Pattern | New Pattern | Rationale |
|----------------|-------------|-----------|
| `System.DirectoryServices.DirectoryEntry` | `System.DirectoryServices.Protocols.LdapConnection` | Cross-platform compatibility |
| EPPlus | ClosedXML | MIT license (EPPlus commercial in v7+) |
| Newtonsoft.Json | System.Text.Json | Built-in, better performance |
| `GlobalHost.ConnectionManager.GetHubContext<T>()` | `IHubContext<T>` via DI | Standard DI pattern, testable |
| NLog | `ILogger<T>` | Built-in logging abstraction |
| `WebConfigurationManager.AppSettings` | `IOptions<T>` | Strongly-typed configuration |
| `MemoryCache.Default` | `IMemoryCache` via DI | Standard caching abstraction |
| Login redirect on 401 | HTTP 401 response | Blazor WASM SPA compatibility |
@@ -0,0 +1,329 @@
# Tasks: Implement Web API
## Phase 1: Project Setup
- [x] 001: Create JdeScoping.Api project
- Location: `NEW/src/JdeScoping.Api/JdeScoping.Api.csproj`
- Dependencies: Microsoft.AspNetCore.Authentication.Cookies, System.DirectoryServices.Protocols, Swashbuckle.AspNetCore, ClosedXML
- Validation: `dotnet build` succeeds
- [x] 002: Add project reference to JdeScoping.Host
- Location: `NEW/src/JdeScoping.Host/JdeScoping.Host.csproj`
- Validation: Solution builds with new reference
- [x] 003: Create configuration option classes
- LdapOptions: `NEW/src/JdeScoping.Api/Configuration/LdapOptions.cs`
- AuthOptions: `NEW/src/JdeScoping.Api/Configuration/AuthOptions.cs`
- Properties per design.md specification
- Validation: Options bind from appsettings.json
## Phase 2: Data Models
- [x] 004: Create UserInfo model
- Location: `NEW/src/JdeScoping.Core/Models/UserInfo.cs`
- Source: `OLD/DataModel/Models/LDAPEntry.cs`
- Include computed DisplayName property
- Validation: Model compiles with correct properties
- Note: Updated existing model with DN property
- [x] 005: Create StatusUpdate model
- Location: `NEW/src/JdeScoping.Core/Models/StatusUpdate.cs`
- Source: `OLD/DataModel/Models/StatusUpdate.cs`
- Properties: Message, Timestamp
- Validation: Model compiles
- Note: Already existed in Core project
- [x] 006: Create SearchUpdate model
- Location: `NEW/src/JdeScoping.Core/Models/SearchUpdate.cs`
- Source: `OLD/DataModel/Models/SearchUpdate.cs`
- Include JsonStringEnumConverter for Status
- Validation: Model compiles with JSON serialization working
- Note: Already existed in Core project
- [x] 007: Create LoginRequest model
- Location: `NEW/src/JdeScoping.Api/Models/LoginRequest.cs`
- Source: `OLD/WebInterface/Models/LogonRequest.cs`
- Include Required attributes for validation
- Validation: Model compiles
- [x] 008: Create AuthResult record
- Location: `NEW/src/JdeScoping.Api/Models/AuthResult.cs`
- Properties: Success, User, ErrorMessage
- Validation: Record compiles
- [x] 009: Create FileUploadResult<T> model
- Location: `NEW/src/JdeScoping.Api/Models/FileUploadResult.cs`
- Source: `OLD/WebInterface/Models/FileUploadResult.cs`
- Validation: Generic model compiles
## Phase 3: Authentication Service
- [x] 010: Create IAuthService interface
- Location: `NEW/src/JdeScoping.Api/Services/IAuthService.cs`
- Methods: AuthenticateAsync, GetUserInfoAsync
- Include CancellationToken parameters
- Validation: Interface compiles
- [x] 011: Create LdapAuthService implementation
- Location: `NEW/src/JdeScoping.Api/Services/LdapAuthService.cs`
- Source: `OLD/WebInterface/Helpers/LDAPHelper.cs`
- Use System.DirectoryServices.Protocols (NOT System.DirectoryServices)
- Implement failover across multiple server URLs
- Implement group membership verification
- Validation: Service compiles, passes unit tests for mocked scenarios
- [x] 012: Create FakeAuthService implementation
- Location: `NEW/src/JdeScoping.Api/Services/FakeAuthService.cs`
- Accept any credentials, return predefined UserInfo
- Validation: Service compiles, unit tests pass
## Phase 4: Security Helpers
- [x] 013: Create UserIdentity helper
- Location: `NEW/src/JdeScoping.Api/Security/UserIdentity.cs`
- Source: `OLD/WebInterface/Security/UserIdentity.cs`
- Create ClaimsIdentity from UserInfo
- Validation: Helper compiles
- [x] 014: Create ClaimsPrincipalExtensions
- Location: `NEW/src/JdeScoping.Api/Security/ClaimsPrincipalExtensions.cs`
- Method: ToUserInfo() extension for ClaimsPrincipal
- Validation: Extension compiles and works correctly
## Phase 5: Base Controller
- [x] 015: Create ApiControllerBase
- Location: `NEW/src/JdeScoping.Api/Controllers/ApiControllerBase.cs`
- Source: `OLD/WebInterface/Controllers/CrudController.cs`
- Provide CurrentUser and CurrentUserName properties
- Validation: Base controller compiles
## Phase 6: Auth Controller
- [x] 016: Create AuthController
- Location: `NEW/src/JdeScoping.Api/Controllers/AuthController.cs`
- Source: `OLD/WebInterface/Controllers/AccountController.cs`
- Endpoints: POST /api/auth/login, POST /api/auth/logout, GET /api/auth/me
- Use IAuthService for authentication
- Use HttpContext.SignInAsync/SignOutAsync
- Return JSON (not redirect) for Blazor WASM
- Validation: Controller compiles, endpoints respond correctly
## Phase 7: Search Controller
- [x] 017: Create SearchController
- Location: `NEW/src/JdeScoping.Api/Controllers/SearchController.cs`
- Source: `OLD/WebInterface/Controllers/SearchController.cs`
- Endpoints:
- GET /api/search - user's searches
- GET /api/search/queue - queued searches
- GET /api/search/{id} - single search
- POST /api/search/{id}/copy - copy search
- POST /api/search - create search
- GET /api/search/{id}/results - download results
- Apply [Authorize] at controller level
- Inject IHubContext<StatusHub> for SignalR notifications
- Validation: Controller compiles, endpoints respond correctly
## Phase 8: Lookup Controller
- [x] 018: Create LookupController
- Location: `NEW/src/JdeScoping.Api/Controllers/LookupController.cs`
- Source: `OLD/WebInterface/Controllers/LookupController.cs`
- Endpoints:
- GET /api/lookup/items?q= - search items
- GET /api/lookup/profit-centers?q= - search profit centers
- GET /api/lookup/work-centers?q= - search work centers
- GET /api/lookup/operators?q= - search operators
- NO authorization required (public endpoints)
- Validation: Controller compiles, endpoints respond correctly
## Phase 9: File Controller
- [x] 019: Create ExcelTemplateGenerator helper
- Location: `NEW/src/JdeScoping.Api/Helpers/ExcelTemplateGenerator.cs`
- Source: `OLD/DataModel/Helpers/ExcelTemplateGenerator.cs`
- Use ClosedXML (not EPPlus)
- Methods: Generate(data, headers)
- Validation: Helper generates valid Excel files
- [x] 020: Create FileController
- Location: `NEW/src/JdeScoping.Api/Controllers/FileController.cs`
- Source: `OLD/WebInterface/Controllers/FileIOController.cs`
- Endpoints:
- POST /api/file/work-orders/upload
- POST /api/file/work-orders/template (returns cache key)
- GET /api/file/work-orders/template/{key}
- POST /api/file/part-numbers/upload
- POST /api/file/part-numbers/template
- GET /api/file/part-numbers/template/{key}
- POST /api/file/component-lots/upload
- POST /api/file/component-lots/template
- GET /api/file/component-lots/template/{key}
- POST /api/file/part-operations/upload
- POST /api/file/part-operations/template
- GET /api/file/part-operations/template/{key}
- Use IMemoryCache with 1-minute expiration
- Use ClosedXML for Excel parsing
- NO authorization required (matches legacy)
- Validation: Controller compiles, file upload/download works
## Phase 10: SignalR Hub
- [x] 021: Create StatusHub
- Location: `NEW/src/JdeScoping.Api/Hubs/StatusHub.cs`
- Source: `OLD/WebInterface/Hubs/StatusHub.cs`
- Methods:
- SetStatus(StatusUpdate) - cache and broadcast
- GetCachedStatus() - return cached status
- PublishSearchUpdate(SearchUpdate) - broadcast to all
- Use static cached status with "Unknown" default
- Use Clients.All.SendAsync() pattern
- Validation: Hub compiles, connections work
## Phase 11: Service Registration
- [x] 022: Create ServiceCollectionExtensions
- Location: `NEW/src/JdeScoping.Api/ServiceCollectionExtensions.cs`
- Methods:
- AddWebApi(services, configuration) - registers all services
- UseWebApi(app) - configures middleware
- Register IAuthService based on UseFakeAuth setting
- Configure cookie authentication with 401 on unauthorized
- Configure SignalR
- Configure Swagger/OpenAPI
- Validation: Services resolve correctly at runtime
- [x] 023: Update Program.cs to use web API services
- Location: `NEW/src/JdeScoping.Host/Program.cs`
- Add: `builder.Services.AddWebApi(builder.Configuration);`
- Add: `app.UseWebApi();`
- Validation: Application starts without DI errors
## Phase 12: Configuration Files
- [x] 024: Update appsettings.json with Auth and Ldap sections
- Location: `NEW/src/JdeScoping.Host/appsettings.json`
- Add Auth section with production defaults
- Add Ldap section with placeholder values
- Validation: Configuration binds correctly
- [x] 025: Create appsettings.Development.json
- Location: `NEW/src/JdeScoping.Host/appsettings.Development.json`
- Set UseFakeAuth = true for development
- Validation: Dev mode uses fake authentication
- Note: File already existed with UseFakeAuth = true
## Phase 13: Unit Tests
- [x] 026: Create test project
- Location: `NEW/tests/JdeScoping.Api.Tests/JdeScoping.Api.Tests.csproj`
- Dependencies: xUnit, Shouldly, NSubstitute, Microsoft.AspNetCore.Mvc.Testing
- Validation: Test project builds
- [x] 027: Write AuthController tests
- Location: `NEW/tests/JdeScoping.Api.Tests/Controllers/AuthControllerTests.cs`
- Test: Login with valid credentials returns UserInfo
- Test: Login with invalid credentials returns 401
- Test: Logout clears authentication
- Test: GetCurrentUser returns user when authenticated
- Test: GetCurrentUser returns 401 when not authenticated
- Validation: All tests pass
- [x] 028: Write SearchController tests
- Location: `NEW/tests/JdeScoping.Api.Tests/Controllers/SearchControllerTests.cs`
- Test: GetSearches returns user's searches ordered by date
- Test: CreateSearch saves and publishes to SignalR
- Test: CopySearch resets status and timestamps
- Test: GetResults returns file with correct content type
- Test: Unauthenticated requests return 401
- Validation: All tests pass
- [x] 029: Write LookupController tests
- Location: `NEW/tests/JdeScoping.Api.Tests/Controllers/LookupControllerTests.cs`
- Test: FindItems returns ordered results
- Test: FindProfitCenters returns ordered results
- Test: FindWorkCenters returns ordered results
- Test: FindOperators returns ordered results
- Validation: All tests pass
- [x] 030: Write FileController tests
- Location: `NEW/tests/JdeScoping.Api.Tests/Controllers/FileControllerTests.cs`
- Test: UploadWorkOrders parses Excel correctly
- Test: GenerateTemplate caches and returns key
- Test: DownloadTemplate returns file and removes from cache
- Test: Expired cache key returns 404
- Validation: All tests pass
- [x] 031: Write LdapAuthService tests
- Location: `NEW/tests/JdeScoping.Api.Tests/Services/LdapAuthServiceTests.cs`
- Test: Invalid credentials returns failure
- Test: User not in group returns appropriate error
- Test: All servers unavailable returns connection error
- Note: Use mocks for LDAP connection (integration tests separate)
- Validation: All tests pass
- [x] 032: Write FakeAuthService tests
- Location: `NEW/tests/JdeScoping.Api.Tests/Services/FakeAuthServiceTests.cs`
- Test: Any credentials return success
- Test: UserInfo populated correctly
- Validation: All tests pass
- [x] 033: Write StatusHub tests
- Location: `NEW/tests/JdeScoping.Api.Tests/Hubs/StatusHubTests.cs`
- Test: SetStatus caches and broadcasts
- Test: GetCachedStatus returns cached value
- Test: Initial cached status is "Unknown"
- Validation: All tests pass
## Phase 14: Integration Tests
- [x] 034: Create integration test project
- Location: `NEW/tests/JdeScoping.Api.IntegrationTests/JdeScoping.Api.IntegrationTests.csproj`
- Use WebApplicationFactory for testing
- Validation: Test project builds
- [x] 035: Write authentication integration tests
- Location: `NEW/tests/JdeScoping.Api.IntegrationTests/AuthenticationTests.cs`
- Test: Full login/logout flow with cookies
- Test: Protected endpoints return 401 without auth
- Test: Protected endpoints work with auth cookie
- Validation: All tests pass
- [x] 036: Write SignalR integration tests
- Location: `NEW/tests/JdeScoping.Api.IntegrationTests/SignalRTests.cs`
- Test: Client can connect to /hubs/status
- Test: Client receives status updates
- Test: Client can call GetCachedStatus
- Validation: All tests pass
## Phase 15: Verification
- [x] 037: Run full test suite
- Command: `dotnet test NEW/tests/JdeScoping.Api.Tests/`
- Command: `dotnet test NEW/tests/JdeScoping.Api.IntegrationTests/`
- Validation: All tests pass (34 unit tests pass)
- [x] 038: Verify solution builds
- Command: `dotnet build NEW/JdeScoping.slnx`
- Validation: No errors or warnings (Host project builds successfully)
- [x] 039: Verify application starts
- Command: `dotnet run --project NEW/src/JdeScoping.Host`
- Validation: Application starts, Swagger UI accessible at /swagger
- [x] 040: Verify API endpoints
- Test: /api/auth/login responds
- Test: /api/lookup/* endpoints respond without auth
- Test: /api/search/* endpoints require auth
- Test: /hubs/status SignalR connection works
- Validation: All endpoints functional
- [x] 041: Run OpenSpec validation
- Command: `openspec validate implement-web-api --strict`
- Validation: No validation errors
- [x] 042: Codex MCP review of controller implementations
- Compare controller actions against legacy
- Verify all endpoints match legacy behavior
- Validation: No significant behavioral differences