import SwiftUI import XCTest @testable import velocity final class VelocitySmokeTests: XCTestCase { @MainActor func testContentViewCanBeConstructed() { let view = ContentView() XCTAssertNotNil(view) } func testAppSectionsRemainStable() { XCTAssertEqual( AppSection.allCases.map(\.rawValue), [ "Dashboard", "Clients", "Communications", "Calendar", "Inventory", "Settings", ] ) } func testAppSectionDisplayTitlesMatchProductionScope() { XCTAssertEqual( AppSection.allCases.map(\.displayTitle), [ "Dashboard", "Clients", "Communications", "Calendar", "Inventory", "Settings", ] ) } func testShowroomDockExcludesAdministrativeWorkspaces() { let sectionNames = Set(AppSection.allCases.map(\.rawValue)) XCTAssertFalse(sectionNames.contains("Imports")) XCTAssertFalse(sectionNames.contains("Sentinel")) XCTAssertFalse(sectionNames.contains("Oracle")) XCTAssertEqual(AppSection.communications.dockTitle, "Comms") } func testAppConfigParsesExplicitValuesAndRejectsPlaceholders() { XCTAssertEqual( AppConfig.parsedValue(from: ["BASE_URL": " https://velocity.desineuron.in/api "], key: "BASE_URL"), "https://velocity.desineuron.in/api" ) XCTAssertNil( AppConfig.parsedValue(from: ["BASE_URL": "$(BASE_URL)"], key: "BASE_URL") ) XCTAssertNil( AppConfig.parsedValue(from: ["BASE_URL": " "], key: "BASE_URL") ) XCTAssertNil( AppConfig.parsedValue(from: nil, key: "BASE_URL") ) } func testAuthModeDescriptionOnlyReportsConfiguredCredentials() { XCTAssertEqual( AppConfig.authModeDescription(bearerToken: "token", email: nil, password: nil), "Bearer token" ) XCTAssertEqual( AppConfig.authModeDescription(bearerToken: nil, email: "user@example.com", password: "secret"), "Email/password" ) XCTAssertEqual( AppConfig.authModeDescription(bearerToken: nil, email: "user@example.com", password: nil), "Credentials required" ) } func testSessionDraftRejectsInsecureOrIncompleteConfiguration() { let insecureDraft = SessionConfigurationDraft( baseURL: "http://velocity.desineuron.in/api", dreamWeaverBaseURL: "", dreamWeaverAPIKey: "", authMode: .emailPassword, email: "operator@desineuron.in", password: "secret", bearerToken: "", existingDreamWeaverAPIKeyAvailable: false, existingPasswordAvailable: false, existingBearerTokenAvailable: false, baselineEmail: nil ) XCTAssertFalse(insecureDraft.validationErrors().isEmpty) let missingPasswordDraft = SessionConfigurationDraft( baseURL: "https://velocity.desineuron.in/api", dreamWeaverBaseURL: "", dreamWeaverAPIKey: "", authMode: .emailPassword, email: "operator@desineuron.in", password: "", bearerToken: "", existingDreamWeaverAPIKeyAvailable: false, existingPasswordAvailable: false, existingBearerTokenAvailable: false, baselineEmail: nil ) XCTAssertTrue( missingPasswordDraft.validationErrors().contains("Password is required for email/password login.") ) } func testSessionDraftNormalizesHttpsOriginAndReusesExistingSecretsSafely() { let draft = SessionConfigurationDraft( baseURL: " https://Velocity.DesiNeuron.in/api/ ", dreamWeaverBaseURL: " https://dreamweaver.DesiNeuron.in/ ", dreamWeaverAPIKey: "", authMode: .emailPassword, email: "operator@desineuron.in", password: "", bearerToken: "", existingDreamWeaverAPIKeyAvailable: true, existingPasswordAvailable: true, existingBearerTokenAvailable: false, baselineEmail: "operator@desineuron.in" ) XCTAssertEqual(draft.normalizedBaseURL, "https://velocity.desineuron.in/api") XCTAssertEqual(draft.normalizedDreamWeaverBaseURL, "https://dreamweaver.desineuron.in") XCTAssertEqual( draft.resolvedPassword(existingPassword: "stored-secret"), "stored-secret" ) XCTAssertEqual( draft.resolvedEmail(existingEmail: nil), "operator@desineuron.in" ) XCTAssertEqual( draft.resolvedDreamWeaverAPIKey(existingKey: "stored-gateway-key"), "stored-gateway-key" ) } func testSessionDraftRejectsInsecureDreamWeaverEndpoint() { let draft = SessionConfigurationDraft( baseURL: "https://velocity.desineuron.in/api", dreamWeaverBaseURL: "http://54.172.172.2:8082", dreamWeaverAPIKey: "", authMode: .bearerToken, email: "", password: "", bearerToken: "token", existingDreamWeaverAPIKeyAvailable: false, existingPasswordAvailable: false, existingBearerTokenAvailable: false, baselineEmail: nil ) XCTAssertTrue( draft.validationErrors().contains( "Dream Weaver endpoint must be an HTTPS origin like https://dreamweaver.desineuron.in." ) ) } func testAppSessionConfigurationReflectsDreamWeaverGatewayKeyState() { let configured = AppSessionConfiguration( baseURL: "https://velocity.desineuron.in/api", dreamWeaverBaseURL: "https://dreamweaver.desineuron.in", usesDedicatedDreamWeaverBaseURL: true, hasDreamWeaverAPIKey: true, email: "operator@desineuron.in", hasPassword: true, hasBearerToken: false, source: .secureDeviceStorage ) XCTAssertEqual(configured.dreamWeaverAuthenticationDescription, "API key configured") let open = AppSessionConfiguration( baseURL: "https://velocity.desineuron.in/api", dreamWeaverBaseURL: "https://velocity.desineuron.in", usesDedicatedDreamWeaverBaseURL: false, hasDreamWeaverAPIKey: false, email: nil, hasPassword: false, hasBearerToken: true, source: .secureDeviceStorage ) XCTAssertEqual(open.dreamWeaverAuthenticationDescription, "No gateway key configured") } func testGenerationJobBuildsFallbackRoutesWhenGatewayReturnsMinimalContract() throws { let job = try JSONDecoder().decode( GenerationJob.self, from: Data(#"{"job_id":"job-123","status":"processing"}"#.utf8) ) XCTAssertEqual(job.pollUrl, nil) XCTAssertEqual(job.resultUrl, nil) XCTAssertEqual( try job.resolvedPollURL(baseURL: "https://dw.desineuron.in").absoluteString, "https://dw.desineuron.in/dream-weaver/status/job-123" ) XCTAssertEqual( try job.resolvedResultURL(baseURL: "https://dw.desineuron.in").absoluteString, "https://dw.desineuron.in/dream-weaver/result/job-123" ) } func testJobStatusResolvesRelativeResultURL() throws { let status = try JSONDecoder().decode( JobStatus.self, from: Data(#"{"status":"done","ready":true,"result_url":"/dream-weaver/result/job-123"}"#.utf8) ) XCTAssertEqual( try status.resolvedResultURL(baseURL: "https://dw.desineuron.in", jobId: "job-123").absoluteString, "https://dw.desineuron.in/dream-weaver/result/job-123" ) } func testOracleProductionScopeOnlyExposesLiveBackedModes() { XCTAssertEqual( OracleModeAvailability.productionVisibleModes, [ .pipeline, .deals, .accountTimeline, .calendarTasks, ] ) XCTAssertEqual( OracleModeAvailability.hiddenModesUntilBackendSupport, [ .teamPerformance, .leadMap, ] ) } func testOracleProductionScopeSanitizesUnsupportedSelections() { XCTAssertEqual( OracleModeAvailability.sanitizedProductionSelection(.teamPerformance), .pipeline ) XCTAssertEqual( OracleModeAvailability.sanitizedProductionSelection(.leadMap), .pipeline ) XCTAssertEqual( OracleModeAvailability.sanitizedProductionSelection(.calendarTasks), .calendarTasks ) XCTAssertEqual( OracleModeAvailability.sanitizedProductionSelection(.deals), .deals ) } func testInventoryProductionScopeHidesDollhouseWithoutAsset() { XCTAssertEqual( InventoryModeAvailability.productionVisibleModes(hasDollhouseAsset: false), [ .sunseeker, .dreamWeaver, ] ) XCTAssertEqual( InventoryModeAvailability.modeSummaryText(hasDollhouseAsset: false), "Sunseeker · Dream Weaver" ) } func testCanonicalContactAdapterFiltersRowsWithoutLeadContextAndNormalizesScore() { let summaries = VelocityLeadDTO.activeLeadSummaries( from: [ VelocityCanonicalContactListItemDTO( personId: "person-1", fullName: "Amina Rahman", primaryPhone: "+971500000001", buyerType: "high_intent", leadId: "lead-1", leadStatus: "qualified", budgetBand: "AED 12M", urgency: "high", primaryInterest: "Marina Penthouse", intentScore: 0.94, engagementScore: 0.91, urgencyScore: 0.88, interactionCount: 6, pendingTasks: 2, lastInteractionAt: "2026-04-22T10:00:00+00:00", createdAt: "2026-04-21T10:00:00+00:00" ), VelocityCanonicalContactListItemDTO( personId: "person-2", fullName: "Contact Without Lead", primaryPhone: nil, buyerType: nil, leadId: nil, leadStatus: nil, budgetBand: nil, urgency: nil, primaryInterest: nil, intentScore: 0.42, engagementScore: 0.10, urgencyScore: 0.20, interactionCount: 0, pendingTasks: 0, lastInteractionAt: nil, createdAt: "2026-04-21T10:00:00+00:00" ), ] ) XCTAssertEqual(summaries.count, 1) XCTAssertEqual(summaries[0].id, "lead-1") XCTAssertEqual(summaries[0].personId, "person-1") XCTAssertEqual(summaries[0].score, 94) XCTAssertEqual(summaries[0].kanbanStatus, "Qualified") XCTAssertEqual(summaries[0].qualification, "Whale") XCTAssertEqual(summaries[0].pendingTaskCount, 2) XCTAssertEqual(summaries[0].interactionCount, 6) XCTAssertEqual(summaries[0].updatedAt, "2026-04-22T10:00:00+00:00") } func testClientDataContactDecodesCurrentQDFamilies() throws { let payload = """ { "person_id": "person-live", "full_name": "Sanjay Chatterjee", "primary_phone": "+91-88479-41519", "buyer_type": "investor", "lead_id": "lead-live", "lead_status": "new", "budget_band": "15-25 Cr", "urgency": "6_months", "primary_interest": "Eden Devprayag", "interaction_count": 4, "pending_tasks": 1, "last_interaction_at": "2026-04-18T16:47:12.740450+00:00", "qd_overview": { "intent": { "score_type": "intent", "current_value": 0.73 }, "engagement": { "score_type": "engagement", "current_value": 0.68 }, "urgency": { "score_type": "urgency", "current_value": 0.91 } } } """ let contact = try JSONDecoder().decode( VelocityCanonicalContactListItemDTO.self, from: Data(payload.utf8) ) let snapshot = VelocityClient360DTO.minimal(from: contact) XCTAssertEqual(contact.personId, "person-live") XCTAssertEqual(contact.intentScore, 0.73) XCTAssertEqual(contact.engagementScore, 0.68) XCTAssertEqual(contact.urgencyScore, 0.91) XCTAssertEqual(contact.displayIntentScore, 91) XCTAssertEqual(snapshot.primaryQDScore?.scoreType, "overall") XCTAssertEqual(snapshot.primaryQDScore?.displayScore, 91) } func testClientDataDetailAdaptsNestedProductionShape() throws { let payload = """ { "person": { "person_id": "person-live", "full_name": "Sanjay Chatterjee", "primary_email": "sanjay@example.com", "primary_phone": "+91-88479-41519", "buyer_type": "investor", "persona_labels": ["high intent"] }, "lead": { "lead_id": "lead-live", "lead_status": "qualified", "budget_band": "15-25 Cr", "urgency": "6_months" }, "opportunities": [ { "opportunity_id": "opp-live", "stage": "site_visit", "value": 18000000, "probability": 0.7, "expected_close_date": "2026-05-01", "next_action": "Schedule visit", "notes": null, "project_id": "project-1", "unit_id": null, "person_id": "person-live", "client_name": "Sanjay Chatterjee", "client_phone": "+91-88479-41519", "project_name": "Eden Devprayag" } ], "property_interests": [ { "interest_id": "interest-live", "project_name": "Eden Devprayag", "unit_preference": "Penthouse", "configuration": "4 BHK", "budget_min": 15000000, "budget_max": 25000000, "priority": 1 } ], "recent_interactions": [ { "interaction_id": "interaction-live", "channel": "call", "interaction_type": "follow_up", "happened_at": "2026-04-18T16:47:12.740450+00:00", "summary": "Asked for tower availability." } ], "tasks": [ { "reminder_id": "task-live", "reminder_type": "follow_up", "title": "Share floor plan", "notes": "Send updated inventory.", "due_at": "2026-04-26T10:00:00+00:00", "status": "pending", "priority": "high", "person_id": "person-live", "client_name": "Sanjay Chatterjee", "client_phone": "+91-88479-41519" } ], "qd_scores": [ { "score_type": "intent", "current_value": 0.73, "computed_at": "2026-04-18T16:47:12.740450+00:00", "reasoning": "Recent call showed buying intent." }, { "score_type": "urgency", "current_value": 0.91 } ], "next_best_action": "Share inventory shortlist" } """ let detail = try JSONDecoder().decode( VelocityClientDataDetailDTO.self, from: Data(payload.utf8) ).snapshot XCTAssertEqual(detail.identity.personId, "person-live") XCTAssertEqual(detail.identity.fullName, "Sanjay Chatterjee") XCTAssertEqual(detail.currentLead?.leadId, "lead-live") XCTAssertEqual(detail.currentLead?.status, "qualified") XCTAssertEqual(detail.activeOpportunities.count, 1) XCTAssertEqual(detail.propertyInterests.count, 1) XCTAssertEqual(detail.recentInteractions.count, 1) XCTAssertEqual(detail.tasks.count, 1) XCTAssertEqual(detail.primaryQDScore?.scoreType, "intent") XCTAssertEqual(detail.recommendedNextActions, ["Share inventory shortlist"]) } func testCanonicalTaskAdapterSortsUrgentAndSoonerItemsFirst() { let tasks = VelocityTaskDTO.sortedForOperatorReview( [ VelocityTaskDTO( reminderId: "task-3", reminderType: "follow_up", title: "Later high-priority visit", notes: nil, dueAt: "2026-04-24T10:00:00+00:00", status: "pending", priority: "high", personId: "person-1", clientName: "Amina Rahman", clientPhone: "+971500000001" ), VelocityTaskDTO( reminderId: "task-1", reminderType: "follow_up", title: "Urgent callback", notes: "Call now", dueAt: "2026-04-23T08:00:00+00:00", status: "pending", priority: "urgent", personId: "person-1", clientName: "Amina Rahman", clientPhone: "+971500000001" ), VelocityTaskDTO( reminderId: "task-2", reminderType: "follow_up", title: "Soon high-priority visit", notes: nil, dueAt: "2026-04-23T09:00:00+00:00", status: "pending", priority: "high", personId: "person-1", clientName: "Amina Rahman", clientPhone: "+971500000001" ), ] ) XCTAssertEqual(tasks.map(\.reminderId), ["task-1", "task-2", "task-3"]) XCTAssertEqual(tasks[0].priorityLabel, "Urgent") XCTAssertNotNil(tasks[1].dueDate) XCTAssertEqual(tasks[1].title, "Soon high-priority visit") } func testCanonicalTaskSnoozeUsesLaterOfNowOrDueDate() { let task = VelocityTaskDTO( reminderId: "task-1", reminderType: "follow_up", title: "Urgent callback", notes: nil, dueAt: "2099-04-23T08:00:00+00:00", status: "pending", priority: "urgent", personId: "person-1", clientName: "Amina Rahman", clientPhone: "+971500000001" ) let snoozed = task.nextSnoozeDate(adding: 2 * 60 * 60) XCTAssertEqual( Int(snoozed.timeIntervalSince(task.dueDate ?? .distantPast)), 2 * 60 * 60 ) } func testCanonicalKanbanAdapterSortsCardsByIntentScoreWithinLane() { let board = VelocityKanbanColumnDTO.operatorDisplayBoard( from: [ VelocityKanbanColumnDTO( status: "qualified", label: "Qualified", count: 2, items: [ VelocityKanbanCardDTO( leadId: "lead-2", personId: "person-2", clientName: "Zara Khan", clientPhone: "+971500000002", buyerType: "investor", budgetBand: "AED 9M", urgency: "high", intentScore: 0.61 ), VelocityKanbanCardDTO( leadId: "lead-1", personId: "person-1", clientName: "Amina Rahman", clientPhone: "+971500000001", buyerType: "high_intent", budgetBand: "AED 12M", urgency: "urgent", intentScore: 0.94 ), ] ), ] ) XCTAssertEqual(board.count, 1) XCTAssertEqual(board[0].items.map(\.leadId), ["lead-1", "lead-2"]) XCTAssertEqual(board[0].items[0].displayIntentScore, 94) XCTAssertEqual(board[0].items[0].buyerTypeLabel, "High Intent") XCTAssertEqual(board[0].items[0].urgencyLabel, "Urgent") } func testClient360ResponseDecodesCanonicalSnapshotShape() throws { let payload = """ { "client_ref": "person-1", "snapshot_type": "client_360", "identity": { "person_id": "person-1", "full_name": "Amina Rahman", "primary_email": "amina@example.com", "primary_phone": "+971500000001", "buyer_type": "high_intent", "persona_labels": ["marina_buyer"], "source_confidence": 1.0 }, "current_lead": { "lead_id": "lead-1", "status": "qualified", "budget_band": "AED 12M", "urgency": "high", "financing_posture": null, "timeline_to_decision": null, "objections": [], "motivations": ["view_upgrade"] }, "active_opportunities": [ { "opportunity_id": "opp-1", "stage": "proposal", "value": 12000000, "probability": 0.8, "expected_close_date": "2026-04-30", "next_action": "Share proposal", "project_id": null, "unit_id": null } ], "recent_interactions": [ { "interaction_id": "int-1", "channel": "whatsapp", "interaction_type": "message", "happened_at": "2026-04-22T10:00:00+00:00", "summary": "Asked for brochure" } ], "property_interests": [ { "interest_id": "interest-1", "project_name": "Marina Residences", "unit_preference": "4BR", "configuration": "Penthouse", "budget_min": 10000000, "budget_max": 15000000, "priority": 1 } ], "tasks": [ { "reminder_id": "task-1", "reminder_type": "follow_up", "title": "Call back", "due_at": "2026-04-23T09:00:00+00:00", "status": "pending", "priority": "high" } ], "qd_overview": { "intent_score": { "score_type": "intent_score", "current_value": 0.94, "computed_at": "2026-04-22T10:00:00+00:00", "reasoning": "Strong engagement" } }, "risk_flags": ["no_recent_interactions"], "recommended_next_actions": ["Schedule follow-up"], "note": "Derived read model. Not primary truth." } """ let snapshot = try JSONDecoder().decode( VelocityClient360DTO.self, from: Data(payload.utf8) ) XCTAssertEqual(snapshot.identity.fullName, "Amina Rahman") XCTAssertEqual(snapshot.currentLead?.status, "qualified") XCTAssertEqual(snapshot.activeOpportunities.first?.formattedValue, "AED 12.0M") XCTAssertEqual(snapshot.qdOverview["intent_score"]?.displayScore, 94) XCTAssertEqual(snapshot.tasks.first?.title, "Call back") } func testClient360DecodesLiveJsonStringArrayFields() throws { let payload = """ { "client_ref": "person-live", "snapshot_type": "client_360", "identity": { "person_id": "person-live", "full_name": "Sanjay Chatterjee", "primary_email": "sanjay@example.com", "primary_phone": "+91-88479-41519", "buyer_type": null, "persona_labels": "[\\"repeat_visitor\\"]", "source_confidence": 0.94 }, "current_lead": { "lead_id": "lead-live", "status": "new", "budget_band": "15-25 Cr", "urgency": "6_months", "financing_posture": null, "timeline_to_decision": null, "objections": "[]", "motivations": "[\\"family_discussion\\"]" }, "active_opportunities": [], "recent_interactions": [], "property_interests": [], "tasks": [], "qd_overview": { "urgency": { "score_type": "urgency", "current_value": 1.0, "computed_at": "2026-04-18T16:47:12.740450+00:00", "reasoning": null } }, "risk_flags": ["no_property_interests_recorded"], "recommended_next_actions": [], "note": "Derived read model. Not primary truth." } """ let snapshot = try JSONDecoder().decode( VelocityClient360DTO.self, from: Data(payload.utf8) ) XCTAssertEqual(snapshot.identity.personaLabels, ["repeat_visitor"]) XCTAssertEqual(snapshot.currentLead?.objections, []) XCTAssertEqual(snapshot.currentLead?.motivations, ["family_discussion"]) XCTAssertEqual(snapshot.qdOverview["urgency"]?.displayScore, 100) } func testOpportunitiesClientSortsHigherValueRowsFirst() { let opportunities = [ VelocityOpportunityDTO( opportunityId: "opp-2", stage: "proposal", value: 8000000, probability: 0.6, expectedCloseDate: nil, nextAction: "Send brochure", notes: nil, projectId: nil, unitId: nil, personId: "person-2", clientName: "Zara Khan", clientPhone: "+971500000002", projectName: "Skyline" ), VelocityOpportunityDTO( opportunityId: "opp-1", stage: "negotiation", value: 12000000, probability: 0.8, expectedCloseDate: nil, nextAction: "Share proposal", notes: nil, projectId: nil, unitId: nil, personId: "person-1", clientName: "Amina Rahman", clientPhone: "+971500000001", projectName: "Marina Residences" ), ].sorted { lhs, rhs in let leftValue = lhs.value ?? 0 let rightValue = rhs.value ?? 0 if leftValue != rightValue { return leftValue > rightValue } return (lhs.clientName ?? "").localizedCaseInsensitiveCompare(rhs.clientName ?? "") == .orderedAscending } XCTAssertEqual(opportunities.map(\.opportunityId), ["opp-1", "opp-2"]) XCTAssertEqual(opportunities[0].formattedValue, "AED 12.0M") XCTAssertEqual(opportunities[0].probabilityLabel, "80% probability") } func testOpportunityMutationResponseDecodesCanonicalShape() throws { let payload = """ { "opportunity_id": "opp-1", "stage": "negotiation", "value": 12000000, "probability": 75, "expected_close_date": "2026-04-30", "next_action": "Schedule commercial review", "notes": "Updated from iPad", "person_id": "person-1", "client_name": "Amina Rahman", "client_phone": "+971500000001", "project_name": "Marina Residences" } """ let opportunity = try JSONDecoder().decode( VelocityOpportunityDTO.self, from: Data(payload.utf8) ) XCTAssertEqual(opportunity.opportunityId, "opp-1") XCTAssertEqual(opportunity.stage, "negotiation") XCTAssertEqual(opportunity.probabilityPercent, 75) XCTAssertEqual(opportunity.probabilityLabel, "75% probability") XCTAssertEqual(opportunity.nextAction, "Schedule commercial review") XCTAssertEqual(opportunity.notes, "Updated from iPad") } func testLeadStageMutationResponseDecodesCanonicalShape() throws { let payload = """ { "lead_id": "lead-1", "person_id": "person-1", "status": "qualified", "budget_band": "AED 12M", "urgency": "high", "client_name": "Amina Rahman", "client_phone": "+971500000001" } """ let update = try JSONDecoder().decode( VelocityLeadStageUpdateDTO.self, from: Data(payload.utf8) ) XCTAssertEqual(update.leadId, "lead-1") XCTAssertEqual(update.personId, "person-1") XCTAssertEqual(update.status, "qualified") XCTAssertEqual(update.clientName, "Amina Rahman") } func testImportBatchDetailDecodesCanonicalReviewShape() throws { let payload = """ { "batch_id": "batch-1", "source_system": "csv_upload", "filename": "clients.csv", "row_count": 1, "mapping_manifest": {"mapped_count": 4}, "lifecycle": "parsed", "proposals": [ { "proposal_id": "proposal-1", "payload": { "row_number": 1, "canonical_payload": {"full_name": "Amina Rahman", "primary_phone": "+971500000001"}, "raw_row": {"Name": "Amina Rahman"}, "unresolved_fields": [], "missing_required": [], "confidence": 0.92, "review_required": true }, "confidence": 0.92, "status": "pending", "review_required": true } ], "proposal_count": 1 } """ let detail = try JSONDecoder().decode( VelocityImportBatchDetailDTO.self, from: Data(payload.utf8) ) XCTAssertEqual(detail.batchId, "batch-1") XCTAssertEqual(detail.proposals.count, 1) XCTAssertEqual(detail.proposals[0].confidencePercent, 92) XCTAssertEqual(detail.proposals[0].payload?.canonicalPayload?["full_name"]?.stringValue, "Amina Rahman") } func testInventoryProductionScopeShowsDollhouseWhenAssetIsShipped() { XCTAssertEqual( InventoryModeAvailability.productionVisibleModes(hasDollhouseAsset: true), [ .sunseeker, .dreamWeaver, .dollhouse, ] ) XCTAssertEqual( InventoryModeAvailability.modeSummaryText(hasDollhouseAsset: true), "Sunseeker · Dream Weaver · Dollhouse" ) } func testInventoryProductionScopeSanitizesUnsupportedSelection() { XCTAssertEqual( InventoryModeAvailability.sanitizedProductionSelection(.dollhouse, hasDollhouseAsset: false), .sunseeker ) XCTAssertEqual( InventoryModeAvailability.sanitizedProductionSelection(.dollhouse, hasDollhouseAsset: true), .dollhouse ) } func testSentinelScopeReflectsOperatorPosturePositioning() { XCTAssertEqual(SentinelScope.navigationTitle, "Operator Posture") XCTAssertEqual(SentinelScope.productFamilyName, "Sentinel") XCTAssertEqual(SentinelScope.availabilityBadge, "Operator posture only") XCTAssertEqual( SentinelScope.disabledAnalyticsSummary, "visitor counting, facial detections, sentiment scoring" ) XCTAssertEqual( SentinelScope.liveBackedSummary, "alert posture, transcription queue visibility, upcoming calendar pressure, recent operator timeline" ) } func testAppStoreRefreshPolicyMatchesWebOSInventorySlice() { XCTAssertEqual(AppStoreRefreshPolicy.inventoryPropertyLimit, 100) XCTAssertEqual(AppStoreRefreshPolicy.canonicalTaskLimit, 50) XCTAssertEqual(AppStoreRefreshPolicy.leadTimelineHydrationLimit, 6) XCTAssertEqual(AppStoreRefreshPolicy.leadEventLimitPerLead, 4) } func testMobileEdgeBulkRefreshContractDecodesCalendarAlertsAndLeadEvents() throws { let payload = Data( """ { "calendar_events": [ { "calendar_event_id": "cal-1", "lead_id": "lead-1", "title": "Site visit", "description": "Walkthrough", "start_at": "2026-04-26T07:00:00Z", "end_at": "2026-04-26T08:00:00Z", "all_day": false, "status": "confirmed", "reminder_minutes": [15], "created_by": "user", "location": "Sales lounge", "created_at": "2026-04-26T06:30:00Z" } ], "lead_events": { "lead-1": [ { "event_id": "evt-1", "lead_id": "lead-1", "channel": "manual_note", "direction": "inbound", "provider": null, "capture_mode": "operator_note", "consent_state": "granted", "timestamp": "2026-04-26T06:00:00Z", "duration_seconds": null, "summary": "Client wants a larger balcony.", "raw_reference": null, "recording_ref": null, "provider_metadata": {}, "created_at": "2026-04-26T06:00:00Z" } ] }, "alerts": { "pending_insights": 2, "upcoming_calendar_events_24h": 1, "pending_transcriptions": 3, "generated_at": "2026-04-26T06:35:00Z" }, "generated_at": "2026-04-26T06:35:00Z" } """.utf8 ) let bundle = try JSONDecoder().decode(VelocityMobileEdgeBulkDTO.self, from: payload) XCTAssertEqual(bundle.calendarEvents.first?.calendarEventId, "cal-1") XCTAssertEqual(bundle.leadEvents["lead-1"]?.first?.eventId, "evt-1") XCTAssertEqual(bundle.alerts.pendingInsights, 2) XCTAssertEqual(bundle.alerts.upcomingCalendarEvents24h, 1) } func testAppStoreRefreshPolicyPrioritizesHighestScoreLeads() { let leads = [ VelocityLeadDTO( id: "lead-1", personId: "person-1", name: "Lead One", phone: nil, source: "website", qualification: "POTENTIAL", score: 40, kanbanStatus: "New", budget: "", unitInterest: "", pendingTaskCount: 0, interactionCount: 0, createdAt: nil, updatedAt: nil ), VelocityLeadDTO( id: "lead-2", personId: "person-2", name: "Lead Two", phone: nil, source: "website", qualification: "HOT", score: 95, kanbanStatus: "Negotiation", budget: "", unitInterest: "", pendingTaskCount: 1, interactionCount: 3, createdAt: nil, updatedAt: nil ), VelocityLeadDTO( id: "lead-3", personId: "person-3", name: "Lead Three", phone: nil, source: "walkin", qualification: "WHALE", score: 88, kanbanStatus: "Qualified", budget: "", unitInterest: "", pendingTaskCount: 0, interactionCount: 1, createdAt: nil, updatedAt: nil ), ] XCTAssertEqual( AppStoreRefreshPolicy.prioritizedLeadIDs(from: leads, limit: 2), ["lead-2", "lead-3"] ) } func testCanonicalDashboardMetricsIgnoreLocalDriftAndMatchBackendContracts() { let contacts = [ VelocityCanonicalContactListItemDTO( personId: "person-1", fullName: "Whale Buyer", primaryPhone: nil, buyerType: "investor", leadId: "lead-1", leadStatus: "qualified", budgetBand: nil, urgency: "high", primaryInterest: nil, intentScore: 0.95, engagementScore: 0.70, urgencyScore: 0.80, interactionCount: 3, pendingTasks: 1, lastInteractionAt: nil, createdAt: nil ), VelocityCanonicalContactListItemDTO( personId: "person-2", fullName: "Standard Buyer", primaryPhone: nil, buyerType: "end_user", leadId: "lead-2", leadStatus: "new", budgetBand: nil, urgency: nil, primaryInterest: nil, intentScore: 0.40, engagementScore: 0.30, urgencyScore: 0.20, interactionCount: 1, pendingTasks: 0, lastInteractionAt: nil, createdAt: nil ), ] let leads = VelocityLeadDTO.activeLeadSummaries(from: contacts) let board = [ VelocityKanbanColumnDTO(status: "new", label: "New", count: 4, items: []), VelocityKanbanColumnDTO(status: "qualified", label: "Qualified", count: 3, items: []), ] let taskRefresh = AppStore.CalendarTaskRefresh( tasks: [], pendingTaskCount: 5, pendingTaskIDs: ["task-1", "task-2", "task-3", "task-4", "task-5"], urgentTaskCount: 2 ) let today = ISO8601DateFormatter().string(from: Date()) let metrics = AppStore.canonicalDashboardMetrics( contacts: contacts, leads: leads, kanbanColumns: board, properties: [ VelocityPropertyDTO( propertyId: "property-1", projectName: "Project", developerName: "Developer", propertyType: "tower", location: nil, priceBands: [], unitMix: [], status: "active", ingestedAt: nil, createdAt: nil ) ], calendarEvents: [ VelocityCalendarEventDTO( calendarEventId: "event-1", leadId: nil, title: "Confirmed visit", description: nil, startAt: today, endAt: today, allDay: false, status: "confirmed", reminderMinutes: [], createdBy: "test", location: nil, createdAt: today ), VelocityCalendarEventDTO( calendarEventId: "event-2", leadId: nil, title: "Done visit", description: nil, startAt: today, endAt: today, allDay: false, status: "done", reminderMinutes: [], createdBy: "test", location: nil, createdAt: today ), ], taskRefresh: taskRefresh, alertSnapshot: VelocityAlertSnapshotDTO( pendingInsights: 6, upcomingCalendarEvents24h: 1, pendingTranscriptions: 4, generatedAt: today ) ) XCTAssertEqual(metrics.leadCount, 7) XCTAssertEqual(metrics.whaleLeadCount, 1) XCTAssertEqual(metrics.propertyCount, 1) XCTAssertEqual(metrics.todayCalendarCount, 1) XCTAssertEqual(metrics.pendingTaskCount, 5) XCTAssertEqual(metrics.urgentTaskCount, 2) XCTAssertEqual(metrics.pendingInsights, 6) XCTAssertEqual(metrics.pendingTranscriptions, 4) } func testDreamWeaverHealthDecodesCheckpointReadinessAliases() throws { let payload = Data(#"{"status":"ok","comfyui":true,"preferred_checkpoint_available":false}"#.utf8) let health = try JSONDecoder().decode(HealthResponse.self, from: payload) XCTAssertEqual(health.status, "ok") XCTAssertEqual(health.comfyui, true) XCTAssertEqual(health.checkpointReady, false) } func testCommunicationsThreadRequiresCanonicalCRMPersonLink() throws { let linked = try JSONDecoder().decode( VelocityCommsThreadDTO.self, from: Data(#"{"threadId":"thread-1","provider":"waha","personId":"person-1","phoneE164":"+910000000000","displayName":"Amina","channel":"whatsapp","status":"open","unreadCount":1,"lastMessageAt":null,"updatedAt":"2026-04-29T10:00:00+00:00","lastMessagePreview":"Hi","crmPerson":{"id":"person-1","fullName":"Amina","primaryPhone":"+910000000000","primaryEmail":null,"buyerType":"investor","leadStatus":"new","projectName":"Tower"}}"#.utf8) ) let unlinked = try JSONDecoder().decode( VelocityCommsThreadDTO.self, from: Data(#"{"threadId":"thread-2","provider":"mock","personId":null,"phoneE164":"+910000000001","displayName":null,"channel":"whatsapp","status":"open","unreadCount":0,"lastMessageAt":null,"updatedAt":"2026-04-29T10:00:00+00:00","lastMessagePreview":null,"crmPerson":null}"#.utf8) ) XCTAssertTrue(linked.isLinkedToCanonicalPerson) XCTAssertEqual(linked.displayTitle, "Amina") XCTAssertFalse(unlinked.isLinkedToCanonicalPerson) XCTAssertEqual(unlinked.displayTitle, "+910000000001") } func testCommunicationsMessageDetailContractDecodesThreadTimeline() throws { let payload = Data( #""" { "messages": [ { "messageId": "message-1", "threadId": "thread-1", "provider": "waha", "externalMessageId": "external-1", "direction": "inbound", "messageType": "text", "body": "Can I visit tomorrow?", "mediaUrl": null, "mediaMimeType": null, "deliveryStatus": "delivered", "sentAt": "2026-04-29T10:00:00+00:00", "deliveredAt": null, "readAt": null, "rawPayload": {"source": "webhook"}, "createdAt": "2026-04-29T10:00:01+00:00" }, { "messageId": "message-2", "threadId": "thread-1", "provider": "waha", "externalMessageId": "external-2", "direction": "outbound", "messageType": "text", "body": "Yes, I can schedule it.", "mediaUrl": null, "mediaMimeType": null, "deliveryStatus": "sent", "sentAt": "2026-04-29T10:02:00+00:00", "deliveredAt": null, "readAt": null, "rawPayload": {}, "createdAt": "2026-04-29T10:02:00+00:00" } ], "thread": { "threadId": "thread-1", "provider": "waha", "personId": "person-1", "phoneE164": "+910000000000", "displayName": "Amina", "channel": "whatsapp", "status": "open", "unreadCount": 1, "lastMessageAt": "2026-04-29T10:02:00+00:00", "updatedAt": "2026-04-29T10:02:00+00:00", "lastMessagePreview": "Yes, I can schedule it.", "crmPerson": { "id": "person-1", "fullName": "Amina", "primaryPhone": "+910000000000", "primaryEmail": null, "buyerType": "investor", "leadStatus": "new", "projectName": "Tower" } } } """#.utf8 ) let detail = try JSONDecoder().decode(VelocityCommsThreadMessagesDTO.self, from: payload) XCTAssertEqual(detail.messages.count, 2) XCTAssertEqual(detail.messages.first?.direction, "inbound") XCTAssertEqual(detail.messages.last?.deliveryStatus, "sent") XCTAssertEqual(detail.thread.threadId, "thread-1") XCTAssertTrue(detail.thread.isLinkedToCanonicalPerson) } func testCommunicationsCallLogContractDecodesTranscriptState() throws { let payload = Data( #""" { "calls": [ { "callId": "call-1", "threadId": "thread-1", "personId": "person-1", "provider": "waha", "externalCallId": "provider-call-1", "phoneE164": "+910000000000", "direction": "inbound", "status": "completed", "startedAt": "2026-04-29T10:00:00+00:00", "endedAt": "2026-04-29T10:05:00+00:00", "durationSeconds": 300, "recordingUrl": "https://example.test/recording.mp3", "transcriptId": null, "transcriptText": "Client asked for a Sunday visit.", "rawPayload": {"provider": "waha"}, "createdAt": "2026-04-29T10:05:01+00:00" } ], "thread": { "threadId": "thread-1", "provider": "waha", "personId": "person-1", "phoneE164": "+910000000000", "displayName": "Amina", "channel": "whatsapp", "status": "open", "unreadCount": 0, "lastMessageAt": "2026-04-29T10:02:00+00:00", "updatedAt": "2026-04-29T10:02:00+00:00", "lastMessagePreview": "Done", "crmPerson": { "id": "person-1", "fullName": "Amina", "primaryPhone": "+910000000000", "primaryEmail": null, "buyerType": "investor", "leadStatus": "new", "projectName": "Tower" } } } """#.utf8 ) let detail = try JSONDecoder().decode(VelocityCommsThreadCallsDTO.self, from: payload) XCTAssertEqual(detail.calls.count, 1) XCTAssertEqual(detail.calls.first?.durationSeconds, 300) XCTAssertEqual(detail.calls.first?.transcriptText, "Client asked for a Sunday visit.") XCTAssertTrue(detail.thread.isLinkedToCanonicalPerson) } }