Files
Project_Velocity/iOS/velocity-ipad/velocityTests/VelocitySmokeTests.swift
sayan eeb684b46c feat: Ipad app production readiness, Colony orchestration, Social posting (#44)
#38 Ipad app production readiness, Colony orchestration, Social posting

Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local>
Reviewed-on: sagnik/Project_Velocity#44
2026-05-03 18:30:38 +05:30

1310 lines
48 KiB
Swift

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)
}
}