@elevasis/core 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +60 -0
- package/dist/index.js +198 -1
- package/dist/organization-model/index.d.ts +60 -0
- package/dist/organization-model/index.js +198 -1
- package/dist/test-utils/index.d.ts +399 -363
- package/dist/test-utils/index.js +198 -1
- package/package.json +3 -3
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +444 -309
- package/src/business/acquisition/activity-events.ts +12 -3
- package/src/business/acquisition/api-schemas.test.ts +315 -4
- package/src/business/acquisition/api-schemas.ts +140 -17
- package/src/business/acquisition/build-templates.ts +44 -0
- package/src/business/acquisition/crm-next-action.test.ts +262 -0
- package/src/business/acquisition/crm-next-action.ts +220 -0
- package/src/business/acquisition/crm-priority.test.ts +216 -0
- package/src/business/acquisition/crm-priority.ts +349 -0
- package/src/business/acquisition/crm-state-actions.test.ts +12 -21
- package/src/business/acquisition/deal-ownership.test.ts +351 -0
- package/src/business/acquisition/deal-ownership.ts +120 -0
- package/src/business/acquisition/derive-actions.test.ts +101 -37
- package/src/business/acquisition/derive-actions.ts +49 -24
- package/src/business/acquisition/index.ts +163 -149
- package/src/business/acquisition/types.ts +48 -4
- package/src/execution/engine/index.ts +4 -3
- package/src/execution/engine/tools/lead-service-types.ts +68 -51
- package/src/execution/engine/tools/platform/acquisition/list-tools.ts +6 -5
- package/src/execution/engine/tools/platform/acquisition/types.ts +3 -1
- package/src/execution/engine/tools/registry.ts +4 -3
- package/src/execution/engine/tools/tool-maps.ts +821 -816
- package/src/organization-model/domains/prospecting.ts +204 -1
- package/src/organization-model/domains/sales.test.ts +218 -0
- package/src/organization-model/domains/sales.ts +558 -366
- package/src/organization-model/types.ts +2 -2
- package/src/platform/constants/versions.ts +1 -1
- package/src/reference/_generated/contracts.md +444 -309
- package/src/supabase/database.types.ts +2978 -2958
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
|
-
// Platform event member schemas (
|
|
4
|
+
// Platform event member schemas (9 members — closed union, stays in @repo/core)
|
|
5
5
|
//
|
|
6
6
|
// Domain-specific events (reply_received, booking_nudge_sent, etc.) live in
|
|
7
7
|
// external/elevasis/operations as CrmDomainActivityEventSchema.
|
|
@@ -66,8 +66,16 @@ const DealCreatedEventSchema = z.object({
|
|
|
66
66
|
timestamp: z.string().datetime()
|
|
67
67
|
})
|
|
68
68
|
|
|
69
|
+
const ActionFailedEventSchema = z.object({
|
|
70
|
+
type: z.literal('action_failed'),
|
|
71
|
+
timestamp: z.string().datetime(),
|
|
72
|
+
actionKey: z.string(),
|
|
73
|
+
errorMessage: z.string(),
|
|
74
|
+
payload: z.record(z.string(), z.unknown()).optional()
|
|
75
|
+
})
|
|
76
|
+
|
|
69
77
|
// ---------------------------------------------------------------------------
|
|
70
|
-
// Union — closed
|
|
78
|
+
// Union — closed 9-member platform union
|
|
71
79
|
// ---------------------------------------------------------------------------
|
|
72
80
|
|
|
73
81
|
export const ActivityEventSchema = z.discriminatedUnion('type', [
|
|
@@ -78,7 +86,8 @@ export const ActivityEventSchema = z.discriminatedUnion('type', [
|
|
|
78
86
|
ApprovalResolvedEventSchema,
|
|
79
87
|
ApprovalStaleEventSchema,
|
|
80
88
|
TaskCreatedEventSchema,
|
|
81
|
-
DealCreatedEventSchema
|
|
89
|
+
DealCreatedEventSchema,
|
|
90
|
+
ActionFailedEventSchema
|
|
82
91
|
])
|
|
83
92
|
|
|
84
93
|
export type ActivityEvent = z.infer<typeof ActivityEventSchema>
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_CRM_PRIORITY_RULE_CONFIG,
|
|
4
|
+
LEAD_GEN_PIPELINE_DEFINITIONS,
|
|
5
|
+
LEAD_GEN_STAGE_CATALOG
|
|
6
|
+
} from '../../organization-model/domains/sales'
|
|
7
|
+
import { CrmPriorityOverrideSchema, evaluateCrmDealPriority, resolveCrmPriorityRuleConfig } from './crm-priority'
|
|
3
8
|
import {
|
|
4
9
|
AddCompaniesToListRequestSchema,
|
|
5
10
|
AddContactsToListRequestSchema,
|
|
@@ -15,6 +20,7 @@ import {
|
|
|
15
20
|
CreateDealTaskRequestSchema,
|
|
16
21
|
CreateListRequestSchema,
|
|
17
22
|
DealDetailResponseSchema,
|
|
23
|
+
DealListItemSchema,
|
|
18
24
|
DealListResponseSchema,
|
|
19
25
|
DealNoteResponseSchema,
|
|
20
26
|
DealStageSchema,
|
|
@@ -44,6 +50,15 @@ import {
|
|
|
44
50
|
|
|
45
51
|
const VALID_UUID = '00000000-0000-4000-8000-000000000001'
|
|
46
52
|
const ISO_TS = '2026-04-27T12:34:56.000Z'
|
|
53
|
+
const PRIORITY = {
|
|
54
|
+
bucketKey: 'waiting' as const,
|
|
55
|
+
rank: 30,
|
|
56
|
+
label: 'Waiting',
|
|
57
|
+
color: 'blue',
|
|
58
|
+
reason: 'No immediate response or follow-up is due.',
|
|
59
|
+
latestActivityAt: ISO_TS,
|
|
60
|
+
nextActionAt: null
|
|
61
|
+
}
|
|
47
62
|
|
|
48
63
|
// ---------------------------------------------------------------------------
|
|
49
64
|
// DealStageSchema
|
|
@@ -414,10 +429,251 @@ describe('UpdateContactRequestSchema', () => {
|
|
|
414
429
|
})
|
|
415
430
|
})
|
|
416
431
|
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
// CRM priority override contract
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
|
|
436
|
+
describe('CrmPriorityOverrideSchema', () => {
|
|
437
|
+
it('accepts a valid partial organization override', () => {
|
|
438
|
+
const result = CrmPriorityOverrideSchema.safeParse({
|
|
439
|
+
enabled: true,
|
|
440
|
+
staleAfterDays: 10,
|
|
441
|
+
bucketOrder: ['needs_response', 'follow_up_due', 'stale', 'waiting', 'closed_low'],
|
|
442
|
+
buckets: {
|
|
443
|
+
needs_response: { label: 'Reply Now', color: 'red', rank: 5 },
|
|
444
|
+
waiting: { label: 'Pending' }
|
|
445
|
+
},
|
|
446
|
+
followUpAfterDaysByStateKey: {
|
|
447
|
+
discovery_link_sent: 2,
|
|
448
|
+
custom_state: 4
|
|
449
|
+
},
|
|
450
|
+
closedStageKeys: ['closed_won', 'closed_lost']
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
expect(result.success).toBe(true)
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
it('rejects needsResponseStateKeys and needsResponseActivityTypes (removed fields)', () => {
|
|
457
|
+
expect(CrmPriorityOverrideSchema.safeParse({ needsResponseStateKeys: ['x'] }).success).toBe(false)
|
|
458
|
+
expect(CrmPriorityOverrideSchema.safeParse({ needsResponseActivityTypes: ['x'] }).success).toBe(false)
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
it('accepts sparse partial overrides', () => {
|
|
462
|
+
const result = CrmPriorityOverrideSchema.safeParse({
|
|
463
|
+
staleAfterDays: 21,
|
|
464
|
+
buckets: {
|
|
465
|
+
stale: { color: 'yellow' }
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
expect(result.success).toBe(true)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
it('rejects invalid unknown input shapes', () => {
|
|
473
|
+
expect(CrmPriorityOverrideSchema.safeParse('bad').success).toBe(false)
|
|
474
|
+
expect(CrmPriorityOverrideSchema.safeParse({ staleAfterDays: -1 }).success).toBe(false)
|
|
475
|
+
expect(CrmPriorityOverrideSchema.safeParse({ buckets: { invalid_bucket: { label: 'Bad' } } }).success).toBe(false)
|
|
476
|
+
})
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
describe('resolveCrmPriorityRuleConfig', () => {
|
|
480
|
+
it('merges valid organization config overrides with default rules', () => {
|
|
481
|
+
const resolved = resolveCrmPriorityRuleConfig({
|
|
482
|
+
crm: {
|
|
483
|
+
priority: {
|
|
484
|
+
staleAfterDays: 7,
|
|
485
|
+
bucketOrder: ['stale', 'needs_response'],
|
|
486
|
+
buckets: {
|
|
487
|
+
stale: { label: 'Dormant', color: 'yellow' },
|
|
488
|
+
needs_response: { rank: 3 }
|
|
489
|
+
},
|
|
490
|
+
followUpAfterDaysByStateKey: { discovery_link_sent: 1 },
|
|
491
|
+
closedStageKeys: ['won', 'lost']
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
expect(resolved.enabled).toBe(true)
|
|
497
|
+
expect(resolved.staleAfterDays).toBe(7)
|
|
498
|
+
expect(resolved.closedStageKeys).toEqual(['won', 'lost'])
|
|
499
|
+
expect(resolved.followUpAfterDaysByStateKey.discovery_link_sent).toBe(1)
|
|
500
|
+
expect(resolved.followUpAfterDaysByStateKey.reply_sent).toBe(
|
|
501
|
+
DEFAULT_CRM_PRIORITY_RULE_CONFIG.followUpAfterDaysByStateKey.reply_sent
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
const staleBucket = resolved.buckets.find((bucket) => bucket.bucketKey === 'stale')
|
|
505
|
+
expect(staleBucket).toMatchObject({ label: 'Dormant', color: 'yellow', rank: 10 })
|
|
506
|
+
|
|
507
|
+
const needsResponseBucket = resolved.buckets.find((bucket) => bucket.bucketKey === 'needs_response')
|
|
508
|
+
expect(needsResponseBucket?.rank).toBe(3)
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
it('falls back to defaults for missing or invalid input', () => {
|
|
512
|
+
expect(resolveCrmPriorityRuleConfig(undefined)).toMatchObject({
|
|
513
|
+
enabled: true,
|
|
514
|
+
staleAfterDays: DEFAULT_CRM_PRIORITY_RULE_CONFIG.staleAfterDays,
|
|
515
|
+
closedStageKeys: DEFAULT_CRM_PRIORITY_RULE_CONFIG.closedStageKeys
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
expect(resolveCrmPriorityRuleConfig({ staleAfterDays: 0 })).toMatchObject({
|
|
519
|
+
enabled: true,
|
|
520
|
+
staleAfterDays: DEFAULT_CRM_PRIORITY_RULE_CONFIG.staleAfterDays,
|
|
521
|
+
closedStageKeys: DEFAULT_CRM_PRIORITY_RULE_CONFIG.closedStageKeys
|
|
522
|
+
})
|
|
523
|
+
})
|
|
524
|
+
})
|
|
525
|
+
|
|
417
526
|
// ---------------------------------------------------------------------------
|
|
418
527
|
// Response schemas — smoke-level forward-compat checks
|
|
419
528
|
// ---------------------------------------------------------------------------
|
|
420
529
|
|
|
530
|
+
describe('evaluateCrmDealPriority', () => {
|
|
531
|
+
const now = '2026-04-30T12:00:00.000Z'
|
|
532
|
+
const baseInput = {
|
|
533
|
+
stage_key: 'interested',
|
|
534
|
+
state_key: null,
|
|
535
|
+
activity_log: [],
|
|
536
|
+
updated_at: '2026-04-29T12:00:00.000Z',
|
|
537
|
+
created_at: '2026-04-01T12:00:00.000Z'
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
it('returns needs_response when the lead replied last (ownership us)', () => {
|
|
541
|
+
// A reply_received inbound event makes ownership === 'us' → needs_response
|
|
542
|
+
const priority = evaluateCrmDealPriority(
|
|
543
|
+
{
|
|
544
|
+
...baseInput,
|
|
545
|
+
state_key: 'discovery_replied',
|
|
546
|
+
activity_log: [{ type: 'reply_received', timestamp: '2026-04-29T10:00:00.000Z' }]
|
|
547
|
+
},
|
|
548
|
+
{ now }
|
|
549
|
+
)
|
|
550
|
+
expect(priority.bucketKey).toBe('needs_response')
|
|
551
|
+
expect(priority.rank).toBe(10)
|
|
552
|
+
expect(priority.reason).toBe('Lead replied last — we owe the next move.')
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
it('returns follow_up_due when configured follow-up window has elapsed', () => {
|
|
556
|
+
const priority = evaluateCrmDealPriority(
|
|
557
|
+
{
|
|
558
|
+
...baseInput,
|
|
559
|
+
state_key: 'discovery_link_sent',
|
|
560
|
+
activity_log: [{ type: 'action_taken', timestamp: '2026-04-25T12:00:00.000Z', actionKey: 'send_link' }],
|
|
561
|
+
updated_at: '2026-04-25T12:00:00.000Z'
|
|
562
|
+
},
|
|
563
|
+
{ now }
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
expect(priority.bucketKey).toBe('follow_up_due')
|
|
567
|
+
expect(priority.nextActionAt).toBe('2026-04-28T12:00:00.000Z')
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
it('returns waiting when the next action is still in the future', () => {
|
|
571
|
+
const priority = evaluateCrmDealPriority(
|
|
572
|
+
{
|
|
573
|
+
...baseInput,
|
|
574
|
+
state_key: 'discovery_link_sent',
|
|
575
|
+
activity_log: [{ type: 'action_taken', timestamp: '2026-04-29T12:00:00.000Z', actionKey: 'send_link' }],
|
|
576
|
+
updated_at: '2026-04-29T12:00:00.000Z'
|
|
577
|
+
},
|
|
578
|
+
{ now }
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
expect(priority.bucketKey).toBe('waiting')
|
|
582
|
+
expect(priority.nextActionAt).toBe('2026-05-02T12:00:00.000Z')
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
it('returns stale when there is no recent meaningful activity', () => {
|
|
586
|
+
const priority = evaluateCrmDealPriority(
|
|
587
|
+
{ ...baseInput, updated_at: '2026-04-01T12:00:00.000Z', created_at: '2026-04-01T12:00:00.000Z' },
|
|
588
|
+
{ now }
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
expect(priority.bucketKey).toBe('stale')
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
it('returns closed_low for closed stages', () => {
|
|
595
|
+
const priority = evaluateCrmDealPriority({ ...baseInput, stage_key: 'closed_lost' }, { now })
|
|
596
|
+
expect(priority.bucketKey).toBe('closed_low')
|
|
597
|
+
expect(priority.rank).toBe(50)
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
it('ignores malformed activity entries without throwing', () => {
|
|
601
|
+
const priority = evaluateCrmDealPriority(
|
|
602
|
+
{
|
|
603
|
+
...baseInput,
|
|
604
|
+
activity_log: [null, 'bad', { type: 'reply_received', timestamp: 'not-a-date' }, { other: true }],
|
|
605
|
+
updated_at: '2026-04-29T12:00:00.000Z'
|
|
606
|
+
},
|
|
607
|
+
{ now }
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
expect(priority.bucketKey).toBe('waiting')
|
|
611
|
+
expect(priority.latestActivityAt).toBe('2026-04-29T12:00:00.000Z')
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
it('returns a neutral waiting priority when CRM priority is disabled', () => {
|
|
615
|
+
const config = resolveCrmPriorityRuleConfig({
|
|
616
|
+
enabled: false,
|
|
617
|
+
buckets: {
|
|
618
|
+
waiting: { label: 'Priority Off', color: 'gray', rank: 999 }
|
|
619
|
+
}
|
|
620
|
+
})
|
|
621
|
+
const priority = evaluateCrmDealPriority(
|
|
622
|
+
{
|
|
623
|
+
...baseInput,
|
|
624
|
+
state_key: 'discovery_replied',
|
|
625
|
+
activity_log: [{ type: 'reply_received', timestamp: '2026-04-30T10:00:00.000Z' }]
|
|
626
|
+
},
|
|
627
|
+
{ config, now }
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
expect(priority).toMatchObject({
|
|
631
|
+
bucketKey: 'waiting',
|
|
632
|
+
label: 'Priority Off',
|
|
633
|
+
color: 'gray',
|
|
634
|
+
rank: 999,
|
|
635
|
+
reason: 'CRM priority evaluation is disabled.',
|
|
636
|
+
nextActionAt: null
|
|
637
|
+
})
|
|
638
|
+
})
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
describe('DealListItemSchema', () => {
|
|
642
|
+
it('accepts a deal with a derived priority object', () => {
|
|
643
|
+
const result = DealListItemSchema.safeParse({
|
|
644
|
+
id: VALID_UUID,
|
|
645
|
+
organization_id: VALID_UUID,
|
|
646
|
+
contact_id: null,
|
|
647
|
+
contact_email: 'test@example.com',
|
|
648
|
+
pipeline_key: 'crm',
|
|
649
|
+
stage_key: 'interested',
|
|
650
|
+
state_key: 'discovery_link_sent',
|
|
651
|
+
activity_log: [],
|
|
652
|
+
discovery_data: null,
|
|
653
|
+
discovery_submitted_at: null,
|
|
654
|
+
discovery_submitted_by: null,
|
|
655
|
+
proposal_data: null,
|
|
656
|
+
proposal_sent_at: null,
|
|
657
|
+
proposal_pdf_url: null,
|
|
658
|
+
signature_envelope_id: null,
|
|
659
|
+
source_list_id: null,
|
|
660
|
+
source_type: null,
|
|
661
|
+
initial_fee: null,
|
|
662
|
+
monthly_fee: null,
|
|
663
|
+
closed_lost_at: null,
|
|
664
|
+
closed_lost_reason: null,
|
|
665
|
+
created_at: ISO_TS,
|
|
666
|
+
updated_at: ISO_TS,
|
|
667
|
+
priority: PRIORITY,
|
|
668
|
+
ownership: null,
|
|
669
|
+
nextAction: null,
|
|
670
|
+
contact: null
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
expect(result.success).toBe(true)
|
|
674
|
+
})
|
|
675
|
+
})
|
|
676
|
+
|
|
421
677
|
describe('DealDetailResponseSchema (forward-compat)', () => {
|
|
422
678
|
const baseDeal = {
|
|
423
679
|
id: VALID_UUID,
|
|
@@ -443,13 +699,39 @@ describe('DealDetailResponseSchema (forward-compat)', () => {
|
|
|
443
699
|
closed_lost_reason: null,
|
|
444
700
|
created_at: ISO_TS,
|
|
445
701
|
updated_at: ISO_TS,
|
|
446
|
-
|
|
702
|
+
priority: PRIORITY,
|
|
703
|
+
ownership: null,
|
|
704
|
+
nextAction: null,
|
|
705
|
+
contact: null,
|
|
706
|
+
conversation: {
|
|
707
|
+
messages: []
|
|
708
|
+
}
|
|
447
709
|
}
|
|
448
710
|
|
|
449
|
-
it('accepts a deal with null contact', () => {
|
|
711
|
+
it('accepts a deal with null contact and an empty conversation', () => {
|
|
450
712
|
expect(DealDetailResponseSchema.safeParse(baseDeal).success).toBe(true)
|
|
451
713
|
})
|
|
452
714
|
|
|
715
|
+
it('accepts conversation messages with preview-derived bodies', () => {
|
|
716
|
+
const withConversation = {
|
|
717
|
+
...baseDeal,
|
|
718
|
+
conversation: {
|
|
719
|
+
messages: [
|
|
720
|
+
{
|
|
721
|
+
id: 'message-1',
|
|
722
|
+
direction: 'inbound',
|
|
723
|
+
fromEmail: 'lead@example.com',
|
|
724
|
+
toEmail: 'sender@example.com',
|
|
725
|
+
subject: 'Re: quick thought',
|
|
726
|
+
body: 'Sure, send it over.',
|
|
727
|
+
sentAt: ISO_TS
|
|
728
|
+
}
|
|
729
|
+
]
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
expect(DealDetailResponseSchema.safeParse(withConversation).success).toBe(true)
|
|
733
|
+
})
|
|
734
|
+
|
|
453
735
|
it('accepts a deal with nested contact that has null company', () => {
|
|
454
736
|
const withContact = {
|
|
455
737
|
...baseDeal,
|
|
@@ -668,6 +950,19 @@ describe('CreateListRequestSchema', () => {
|
|
|
668
950
|
expect(CreateListRequestSchema.safeParse({ name: 'X', status: 'draft' }).success).toBe(true)
|
|
669
951
|
})
|
|
670
952
|
|
|
953
|
+
it('accepts a known prospecting build template id', () => {
|
|
954
|
+
const result = CreateListRequestSchema.safeParse({
|
|
955
|
+
name: 'DTC Subscription Brands',
|
|
956
|
+
buildTemplateId: 'dtc-subscription-apollo-clickup'
|
|
957
|
+
})
|
|
958
|
+
|
|
959
|
+
expect(result.success).toBe(true)
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
it('rejects an unknown prospecting build template id', () => {
|
|
963
|
+
expect(CreateListRequestSchema.safeParse({ name: 'X', buildTemplateId: 'not-a-template' }).success).toBe(false)
|
|
964
|
+
})
|
|
965
|
+
|
|
671
966
|
it('rejects an invalid status', () => {
|
|
672
967
|
expect(CreateListRequestSchema.safeParse({ name: 'X', status: 'active' }).success).toBe(false)
|
|
673
968
|
})
|
|
@@ -710,6 +1005,23 @@ describe('UpdateListRequestSchema', () => {
|
|
|
710
1005
|
expect(UpdateListRequestSchema.safeParse({ batchIds: ['batch-1'] }).success).toBe(true)
|
|
711
1006
|
})
|
|
712
1007
|
|
|
1008
|
+
it('accepts buildTemplateId when the change is explicitly confirmed', () => {
|
|
1009
|
+
expect(
|
|
1010
|
+
UpdateListRequestSchema.safeParse({
|
|
1011
|
+
buildTemplateId: 'dtc-subscription-apollo-clickup',
|
|
1012
|
+
confirmBuildTemplateChange: true
|
|
1013
|
+
}).success
|
|
1014
|
+
).toBe(true)
|
|
1015
|
+
})
|
|
1016
|
+
|
|
1017
|
+
it('rejects buildTemplateId without explicit confirmation', () => {
|
|
1018
|
+
expect(
|
|
1019
|
+
UpdateListRequestSchema.safeParse({
|
|
1020
|
+
buildTemplateId: 'dtc-subscription-apollo-clickup'
|
|
1021
|
+
}).success
|
|
1022
|
+
).toBe(false)
|
|
1023
|
+
})
|
|
1024
|
+
|
|
713
1025
|
it('rejects unknown fields (strict mode)', () => {
|
|
714
1026
|
expect(UpdateListRequestSchema.safeParse({ name: 'X', extra: 'bad' }).success).toBe(false)
|
|
715
1027
|
})
|
|
@@ -1115,7 +1427,6 @@ describe('AcqListResponseSchema (forward-compat)', () => {
|
|
|
1115
1427
|
organizationId: VALID_UUID,
|
|
1116
1428
|
name: 'Test List',
|
|
1117
1429
|
description: null,
|
|
1118
|
-
type: 'standard',
|
|
1119
1430
|
batchIds: [],
|
|
1120
1431
|
instantlyCampaignId: null,
|
|
1121
1432
|
status: 'draft' as const,
|