@elevasis/core 0.18.0 → 0.20.0
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 +82 -1
- package/dist/index.js +353 -171
- package/dist/knowledge/index.d.ts +44 -1
- package/dist/organization-model/index.d.ts +82 -1
- package/dist/organization-model/index.js +353 -171
- package/dist/test-utils/index.d.ts +41 -12
- package/dist/test-utils/index.js +352 -171
- package/package.json +4 -3
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +89 -69
- package/src/auth/multi-tenancy/organizations/__tests__/api-schemas.test.ts +194 -0
- package/src/auth/multi-tenancy/organizations/api-schemas.ts +136 -128
- package/src/business/acquisition/api-schemas.test.ts +199 -15
- package/src/business/acquisition/api-schemas.ts +116 -51
- package/src/business/acquisition/build-templates.test.ts +212 -0
- package/src/business/acquisition/derive-actions.test.ts +1 -1
- package/src/business/acquisition/types.ts +21 -38
- package/src/business/deals/api-schemas.ts +2 -2
- package/src/execution/engine/index.ts +436 -434
- package/src/execution/engine/tools/integration/server/adapters/google-calendar/google-calendar-adapter.ts +428 -0
- package/src/execution/engine/tools/integration/server/adapters/google-calendar/index.ts +2 -0
- package/src/execution/engine/tools/lead-service-types.ts +51 -9
- package/src/execution/engine/tools/platform/acquisition/company-tools.ts +7 -6
- package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +6 -5
- package/src/execution/engine/tools/platform/acquisition/types.ts +20 -9
- package/src/execution/engine/tools/registry.ts +700 -698
- package/src/execution/engine/tools/tool-maps.ts +10 -0
- package/src/execution/external/__tests__/api-schemas.test.ts +127 -0
- package/src/integrations/oauth/__tests__/provider-registry.test.ts +7 -6
- package/src/integrations/oauth/provider-registry.ts +74 -61
- package/src/integrations/oauth/server/credentials.ts +43 -39
- package/src/knowledge/__tests__/queries.test.ts +89 -0
- package/src/organization-model/__tests__/graph.test.ts +108 -2
- package/src/organization-model/__tests__/icons.test.ts +61 -0
- package/src/organization-model/__tests__/knowledge.test.ts +118 -1
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +91 -0
- package/src/organization-model/__tests__/schema.test.ts +122 -0
- package/src/organization-model/__tests__/surface-projection.test.ts +174 -0
- package/src/organization-model/defaults.ts +8 -0
- package/src/organization-model/domains/knowledge.ts +9 -0
- package/src/organization-model/domains/prospecting.ts +347 -226
- package/src/organization-model/domains/sales.ts +40 -30
- package/src/organization-model/graph/build.ts +74 -0
- package/src/organization-model/graph/schema.ts +1 -0
- package/src/organization-model/graph/types.ts +1 -0
- package/src/organization-model/icons.ts +3 -0
- package/src/organization-model/schema.ts +63 -0
- package/src/organization-model/surface-projection.ts +218 -0
- package/src/organization-model/types.ts +9 -1
- package/src/platform/constants/versions.ts +1 -1
- package/src/platform/utils/__tests__/validation.test.ts +1084 -1083
- package/src/platform/utils/validation.ts +425 -425
- package/src/reference/_generated/contracts.md +89 -69
- package/src/server.ts +6 -0
- package/src/supabase/database.types.ts +6 -12
|
@@ -1,128 +1,136 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Organizations Domain - Zod Validation Schemas
|
|
3
|
-
*
|
|
4
|
-
* Validation schemas for organization management endpoints.
|
|
5
|
-
* Includes request bodies, query params, and path params.
|
|
6
|
-
*
|
|
7
|
-
* Security:
|
|
8
|
-
* - All schemas use .strict() to prevent mass assignment attacks
|
|
9
|
-
* - UUID/WorkOS ID validation prevents invalid references
|
|
10
|
-
* - String length limits prevent DoS
|
|
11
|
-
* - Domain and metadata size limits
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { z } from 'zod'
|
|
15
|
-
import { UuidSchema } from '../../../platform/utils/validation'
|
|
16
|
-
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Shared Schemas
|
|
19
|
-
// ============================================================================
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Organization name validation
|
|
23
|
-
* - Alphanumeric, spaces, hyphens, underscores only
|
|
24
|
-
* - 2-100 characters
|
|
25
|
-
*
|
|
26
|
-
* Security: Prevents injection, DoS via long names
|
|
27
|
-
*/
|
|
28
|
-
export const OrganizationNameSchema = z
|
|
29
|
-
.
|
|
30
|
-
.
|
|
31
|
-
.
|
|
32
|
-
.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Organizations Domain - Zod Validation Schemas
|
|
3
|
+
*
|
|
4
|
+
* Validation schemas for organization management endpoints.
|
|
5
|
+
* Includes request bodies, query params, and path params.
|
|
6
|
+
*
|
|
7
|
+
* Security:
|
|
8
|
+
* - All schemas use .strict() to prevent mass assignment attacks
|
|
9
|
+
* - UUID/WorkOS ID validation prevents invalid references
|
|
10
|
+
* - String length limits prevent DoS
|
|
11
|
+
* - Domain and metadata size limits
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from 'zod'
|
|
15
|
+
import { UuidSchema } from '../../../platform/utils/validation'
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Shared Schemas
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Organization name validation
|
|
23
|
+
* - Alphanumeric, spaces, hyphens, underscores only
|
|
24
|
+
* - 2-100 characters
|
|
25
|
+
*
|
|
26
|
+
* Security: Prevents injection, DoS via long names
|
|
27
|
+
*/
|
|
28
|
+
export const OrganizationNameSchema = z
|
|
29
|
+
.string()
|
|
30
|
+
.min(2, 'Organization name must be at least 2 characters')
|
|
31
|
+
.max(100, 'Organization name must be at most 100 characters')
|
|
32
|
+
.trim()
|
|
33
|
+
.regex(
|
|
34
|
+
/^[a-zA-Z0-9\s\-_]+$/,
|
|
35
|
+
'Organization name must contain only letters, numbers, spaces, hyphens, and underscores'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Organization ID validation
|
|
40
|
+
* Supports both UUID and WorkOS org_ prefixed IDs
|
|
41
|
+
*/
|
|
42
|
+
export const OrganizationIdSchema = z.union([
|
|
43
|
+
UuidSchema,
|
|
44
|
+
z.string().regex(/^org_[a-zA-Z0-9]+$/, 'Invalid WorkOS organization ID')
|
|
45
|
+
])
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Organization domain data schema
|
|
49
|
+
*/
|
|
50
|
+
export const OrganizationDomainSchema = z.object({
|
|
51
|
+
domain: z.string().min(3).max(255),
|
|
52
|
+
state: z.enum(['verified', 'pending', 'failed']).optional()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Path Parameters
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Validate organization ID in URL path
|
|
61
|
+
* Used by: GET/PUT/DELETE /organizations/:id
|
|
62
|
+
*/
|
|
63
|
+
export const OrganizationIdParamSchema = z
|
|
64
|
+
.object({
|
|
65
|
+
id: OrganizationIdSchema
|
|
66
|
+
})
|
|
67
|
+
.strict()
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Request Bodies
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create new organization
|
|
75
|
+
* POST /organizations
|
|
76
|
+
*
|
|
77
|
+
* Security:
|
|
78
|
+
* - Name format validated (alphanumeric + spaces + hyphens + underscores)
|
|
79
|
+
* - Domain array size limited (max 10)
|
|
80
|
+
* - Metadata size limited (10KB)
|
|
81
|
+
* - Strict mode prevents unknown field injection
|
|
82
|
+
*/
|
|
83
|
+
export const CreateOrganizationSchema = z
|
|
84
|
+
.object({
|
|
85
|
+
name: OrganizationNameSchema,
|
|
86
|
+
domainData: z.array(OrganizationDomainSchema).max(10).optional(),
|
|
87
|
+
metadata: z
|
|
88
|
+
.record(z.string(), z.unknown())
|
|
89
|
+
.refine((val) => JSON.stringify(val).length <= 10240, 'Metadata must be under 10KB')
|
|
90
|
+
.optional()
|
|
91
|
+
})
|
|
92
|
+
.strict()
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Update organization
|
|
96
|
+
* PUT /organizations/:id
|
|
97
|
+
*
|
|
98
|
+
* Security:
|
|
99
|
+
* - All fields optional (partial update)
|
|
100
|
+
* - Same validation as create
|
|
101
|
+
* - At least one field required (matches documented Update schema convention,
|
|
102
|
+
* see .claude/rules/core-package.md → "api-schemas.ts Pattern")
|
|
103
|
+
*/
|
|
104
|
+
export const UpdateOrganizationSchema = CreateOrganizationSchema.partial()
|
|
105
|
+
.strict()
|
|
106
|
+
.refine((data) => Object.keys(data).length > 0, {
|
|
107
|
+
message: 'At least one field (name, domainData, or metadata) must be provided'
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// ============================================================================
|
|
111
|
+
// Query Parameters
|
|
112
|
+
// ============================================================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* List organizations with filters
|
|
116
|
+
* GET /organizations
|
|
117
|
+
*
|
|
118
|
+
* Security:
|
|
119
|
+
* - Limit bounded (prevents DoS)
|
|
120
|
+
* - WorkOS pagination cursors
|
|
121
|
+
*/
|
|
122
|
+
export const ListOrganizationsQuerySchema = z.object({
|
|
123
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
124
|
+
before: z.string().optional(), // WorkOS pagination cursor
|
|
125
|
+
after: z.string().optional() // WorkOS pagination cursor
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// TypeScript Type Exports
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
// Export inferred types for use in route handlers
|
|
133
|
+
export type CreateOrganizationInput = z.infer<typeof CreateOrganizationSchema>
|
|
134
|
+
export type UpdateOrganizationInput = z.infer<typeof UpdateOrganizationSchema>
|
|
135
|
+
export type ListOrganizationsQuery = z.infer<typeof ListOrganizationsQuerySchema>
|
|
136
|
+
export type OrganizationIdParam = z.infer<typeof OrganizationIdParamSchema>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import {
|
|
3
|
+
CRM_PIPELINE_DEFINITION,
|
|
3
4
|
DEFAULT_CRM_PRIORITY_RULE_CONFIG,
|
|
4
5
|
LEAD_GEN_PIPELINE_DEFINITIONS,
|
|
5
6
|
LEAD_GEN_STAGE_CATALOG
|
|
@@ -13,11 +14,15 @@ import {
|
|
|
13
14
|
AcqContactStatusSchema,
|
|
14
15
|
AcqEmailValidSchema,
|
|
15
16
|
AcqListResponseSchema,
|
|
17
|
+
BuildPlanSnapshotSchema,
|
|
16
18
|
CreateArtifactRequestSchema,
|
|
17
19
|
CreateCompanyRequestSchema,
|
|
18
20
|
CreateContactRequestSchema,
|
|
19
21
|
CreateDealNoteRequestSchema,
|
|
20
22
|
CreateDealTaskRequestSchema,
|
|
23
|
+
CrmStageKeySchema,
|
|
24
|
+
CrmStateKeySchema,
|
|
25
|
+
CrmTransitionItemRequestSchema,
|
|
21
26
|
CreateListRequestSchema,
|
|
22
27
|
DealDetailResponseSchema,
|
|
23
28
|
DealListItemSchema,
|
|
@@ -36,6 +41,7 @@ import {
|
|
|
36
41
|
ListStatusSchema,
|
|
37
42
|
PipelineStageSchema,
|
|
38
43
|
ScrapingConfigSchema,
|
|
44
|
+
TransitionDealStateRequestSchema,
|
|
39
45
|
TransitionItemRequestSchema,
|
|
40
46
|
UpdateCompanyRequestSchema,
|
|
41
47
|
UpdateContactRequestSchema,
|
|
@@ -43,6 +49,7 @@ import {
|
|
|
43
49
|
UpdateListRequestSchema,
|
|
44
50
|
UpdateListStatusRequestSchema
|
|
45
51
|
} from './api-schemas'
|
|
52
|
+
import { createBuildPlanSnapshotFromTemplateId } from './build-templates'
|
|
46
53
|
|
|
47
54
|
// ---------------------------------------------------------------------------
|
|
48
55
|
// Helpers
|
|
@@ -65,17 +72,31 @@ const PRIORITY = {
|
|
|
65
72
|
// ---------------------------------------------------------------------------
|
|
66
73
|
|
|
67
74
|
describe('DealStageSchema', () => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
const crmStageKeys = CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)
|
|
76
|
+
const crmStateKeys = CRM_PIPELINE_DEFINITION.stages.flatMap((stage) => stage.states.map((state) => state.stateKey))
|
|
77
|
+
|
|
78
|
+
it('derives CRM stage keys from CRM_PIPELINE_DEFINITION', () => {
|
|
79
|
+
expect(CrmStageKeySchema.options).toEqual(crmStageKeys)
|
|
80
|
+
expect(DealStageSchema.options).toEqual(crmStageKeys)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('derives CRM state keys from CRM_PIPELINE_DEFINITION', () => {
|
|
84
|
+
expect(CrmStateKeySchema.options).toEqual(crmStateKeys)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it.each(crmStageKeys)('accepts canonical stage "%s"', (stage) => {
|
|
88
|
+
expect(DealStageSchema.safeParse(stage).success).toBe(true)
|
|
89
|
+
})
|
|
74
90
|
|
|
75
91
|
it('rejects an unknown stage value', () => {
|
|
76
92
|
expect(DealStageSchema.safeParse('open').success).toBe(false)
|
|
77
93
|
expect(DealStageSchema.safeParse('').success).toBe(false)
|
|
78
94
|
})
|
|
95
|
+
|
|
96
|
+
it('rejects unknown CRM state values', () => {
|
|
97
|
+
expect(CrmStateKeySchema.safeParse('custom_state').success).toBe(false)
|
|
98
|
+
expect(CrmStateKeySchema.safeParse('').success).toBe(false)
|
|
99
|
+
})
|
|
79
100
|
})
|
|
80
101
|
|
|
81
102
|
// ---------------------------------------------------------------------------
|
|
@@ -84,7 +105,7 @@ describe('DealStageSchema', () => {
|
|
|
84
105
|
|
|
85
106
|
describe('TransitionItemRequestSchema', () => {
|
|
86
107
|
const valid = {
|
|
87
|
-
pipelineKey: '
|
|
108
|
+
pipelineKey: 'lead-gen',
|
|
88
109
|
stageKey: 'interested',
|
|
89
110
|
stateKey: null
|
|
90
111
|
}
|
|
@@ -120,11 +141,16 @@ describe('TransitionItemRequestSchema', () => {
|
|
|
120
141
|
})
|
|
121
142
|
|
|
122
143
|
it('accepts all canonical CRM deal stages', () => {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
144
|
+
for (const stageKey of CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)) {
|
|
145
|
+
expect(TransitionItemRequestSchema.safeParse({ pipelineKey: 'crm', stageKey, stateKey: null }).success).toBe(true)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('accepts catalog-derived CRM state keys', () => {
|
|
150
|
+
for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
|
|
151
|
+
stage.states.map((state) => state.stateKey)
|
|
152
|
+
)) {
|
|
153
|
+
expect(TransitionItemRequestSchema.safeParse({ ...valid, stateKey }).success).toBe(true)
|
|
128
154
|
}
|
|
129
155
|
})
|
|
130
156
|
|
|
@@ -156,6 +182,11 @@ describe('TransitionItemRequestSchema', () => {
|
|
|
156
182
|
expect(result.success).toBe(false)
|
|
157
183
|
})
|
|
158
184
|
|
|
185
|
+
it('accepts unknown non-empty stage and state keys for generic substrate transitions', () => {
|
|
186
|
+
expect(TransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'custom_stage' }).success).toBe(true)
|
|
187
|
+
expect(TransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'custom_state' }).success).toBe(true)
|
|
188
|
+
})
|
|
189
|
+
|
|
159
190
|
it('rejects unknown top-level fields (strict mode)', () => {
|
|
160
191
|
const result = TransitionItemRequestSchema.safeParse({ ...valid, unknownField: 'x' })
|
|
161
192
|
expect(result.success).toBe(false)
|
|
@@ -174,6 +205,64 @@ describe('TransitionItemRequestSchema', () => {
|
|
|
174
205
|
})
|
|
175
206
|
})
|
|
176
207
|
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// CrmTransitionItemRequestSchema
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
describe('CrmTransitionItemRequestSchema', () => {
|
|
213
|
+
const valid = {
|
|
214
|
+
pipelineKey: 'crm',
|
|
215
|
+
stageKey: 'interested',
|
|
216
|
+
stateKey: null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
it('accepts catalog-derived CRM stage and state keys', () => {
|
|
220
|
+
for (const stageKey of CRM_PIPELINE_DEFINITION.stages.map((stage) => stage.stageKey)) {
|
|
221
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey }).success).toBe(true)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
|
|
225
|
+
stage.states.map((state) => state.stateKey)
|
|
226
|
+
)) {
|
|
227
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey }).success).toBe(true)
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('rejects non-CRM pipeline keys and unknown CRM keys', () => {
|
|
232
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, pipelineKey: 'lead-gen' }).success).toBe(false)
|
|
233
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stageKey: 'unknown_stage' }).success).toBe(false)
|
|
234
|
+
expect(CrmTransitionItemRequestSchema.safeParse({ ...valid, stateKey: 'unknown_state' }).success).toBe(false)
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// TransitionDealStateRequestSchema
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
describe('TransitionDealStateRequestSchema', () => {
|
|
243
|
+
it('accepts catalog-derived CRM state keys', () => {
|
|
244
|
+
for (const stateKey of CRM_PIPELINE_DEFINITION.stages.flatMap((stage) =>
|
|
245
|
+
stage.states.map((state) => state.stateKey)
|
|
246
|
+
)) {
|
|
247
|
+
expect(TransitionDealStateRequestSchema.safeParse({ stateKey }).success).toBe(true)
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('rejects unknown state values', () => {
|
|
252
|
+
expect(TransitionDealStateRequestSchema.safeParse({ stateKey: 'unknown_state' }).success).toBe(false)
|
|
253
|
+
expect(TransitionDealStateRequestSchema.safeParse({ stateKey: '' }).success).toBe(false)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('preserves strict request schema behavior', () => {
|
|
257
|
+
expect(
|
|
258
|
+
TransitionDealStateRequestSchema.safeParse({
|
|
259
|
+
stateKey: CRM_PIPELINE_DEFINITION.stages[0]?.states[0]?.stateKey,
|
|
260
|
+
extra: 'x'
|
|
261
|
+
}).success
|
|
262
|
+
).toBe(false)
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
177
266
|
// ---------------------------------------------------------------------------
|
|
178
267
|
// ExecuteActionRequestSchema
|
|
179
268
|
// ---------------------------------------------------------------------------
|
|
@@ -416,6 +505,22 @@ describe('UpdateContactRequestSchema', () => {
|
|
|
416
505
|
)
|
|
417
506
|
})
|
|
418
507
|
|
|
508
|
+
it('accepts processingState keyed by the stage catalog', () => {
|
|
509
|
+
const result = UpdateContactRequestSchema.safeParse({
|
|
510
|
+
processingState: {
|
|
511
|
+
[LEAD_GEN_STAGE_CATALOG.verified.key]: {
|
|
512
|
+
status: 'no_result'
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
expect(result.success).toBe(true)
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
it('accepts deprecated pipelineStatus as a compatibility no-op', () => {
|
|
521
|
+
expect(UpdateContactRequestSchema.safeParse({ pipelineStatus: 'emailed' }).success).toBe(true)
|
|
522
|
+
})
|
|
523
|
+
|
|
419
524
|
it('rejects unknown top-level fields (strict mode)', () => {
|
|
420
525
|
expect(UpdateContactRequestSchema.safeParse({ firstName: 'Alice', unknown: 'x' }).success).toBe(false)
|
|
421
526
|
})
|
|
@@ -680,7 +785,7 @@ describe('DealDetailResponseSchema (forward-compat)', () => {
|
|
|
680
785
|
organization_id: VALID_UUID,
|
|
681
786
|
contact_id: null,
|
|
682
787
|
contact_email: 'test@example.com',
|
|
683
|
-
pipeline_key: '
|
|
788
|
+
pipeline_key: 'crm',
|
|
684
789
|
stage_key: null,
|
|
685
790
|
state_key: null,
|
|
686
791
|
activity_log: [],
|
|
@@ -743,7 +848,7 @@ describe('DealDetailResponseSchema (forward-compat)', () => {
|
|
|
743
848
|
title: null,
|
|
744
849
|
headline: null,
|
|
745
850
|
linkedin_url: null,
|
|
746
|
-
|
|
851
|
+
processing_state: null,
|
|
747
852
|
enrichment_data: null,
|
|
748
853
|
company: null
|
|
749
854
|
}
|
|
@@ -881,6 +986,58 @@ describe('PipelineStageSchema', () => {
|
|
|
881
986
|
})
|
|
882
987
|
})
|
|
883
988
|
|
|
989
|
+
// ---------------------------------------------------------------------------
|
|
990
|
+
// BuildPlanSnapshotSchema
|
|
991
|
+
// ---------------------------------------------------------------------------
|
|
992
|
+
|
|
993
|
+
describe('BuildPlanSnapshotSchema', () => {
|
|
994
|
+
const validSnapshot = createBuildPlanSnapshotFromTemplateId('dtc-subscription-apollo-clickup')
|
|
995
|
+
|
|
996
|
+
it('accepts a snapshot generated from a prospecting build template', () => {
|
|
997
|
+
expect(validSnapshot).not.toBeNull()
|
|
998
|
+
expect(BuildPlanSnapshotSchema.safeParse(validSnapshot).success).toBe(true)
|
|
999
|
+
})
|
|
1000
|
+
|
|
1001
|
+
it('rejects a step stageKey outside LEAD_GEN_STAGE_CATALOG', () => {
|
|
1002
|
+
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1003
|
+
...validSnapshot,
|
|
1004
|
+
steps: [{ ...validSnapshot!.steps[0], stageKey: 'made-up-stage' }]
|
|
1005
|
+
})
|
|
1006
|
+
|
|
1007
|
+
expect(result.success).toBe(false)
|
|
1008
|
+
})
|
|
1009
|
+
|
|
1010
|
+
it('rejects a step capabilityKey outside CAPABILITY_REGISTRY', () => {
|
|
1011
|
+
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1012
|
+
...validSnapshot,
|
|
1013
|
+
steps: [{ ...validSnapshot!.steps[0], capabilityKey: 'lead-gen.missing.capability' }]
|
|
1014
|
+
})
|
|
1015
|
+
|
|
1016
|
+
expect(result.success).toBe(false)
|
|
1017
|
+
})
|
|
1018
|
+
|
|
1019
|
+
it('rejects duplicate step ids', () => {
|
|
1020
|
+
const first = validSnapshot!.steps[0]!
|
|
1021
|
+
const second = validSnapshot!.steps[1]!
|
|
1022
|
+
const rest = validSnapshot!.steps.slice(2)
|
|
1023
|
+
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1024
|
+
...validSnapshot,
|
|
1025
|
+
steps: [first, { ...second, id: first.id }, ...rest]
|
|
1026
|
+
})
|
|
1027
|
+
|
|
1028
|
+
expect(result.success).toBe(false)
|
|
1029
|
+
})
|
|
1030
|
+
|
|
1031
|
+
it('rejects dependsOn references to unknown step ids', () => {
|
|
1032
|
+
const result = BuildPlanSnapshotSchema.safeParse({
|
|
1033
|
+
...validSnapshot,
|
|
1034
|
+
steps: [{ ...validSnapshot!.steps[0], dependsOn: ['missing-step'] }]
|
|
1035
|
+
})
|
|
1036
|
+
|
|
1037
|
+
expect(result.success).toBe(false)
|
|
1038
|
+
})
|
|
1039
|
+
})
|
|
1040
|
+
|
|
884
1041
|
// ---------------------------------------------------------------------------
|
|
885
1042
|
// ScrapingConfigSchema
|
|
886
1043
|
// ---------------------------------------------------------------------------
|
|
@@ -1255,6 +1412,33 @@ describe('UpdateCompanyRequestSchema', () => {
|
|
|
1255
1412
|
expect(UpdateCompanyRequestSchema.safeParse({ status: 'invalid' }).success).toBe(true)
|
|
1256
1413
|
})
|
|
1257
1414
|
|
|
1415
|
+
it('accepts processingState keyed by the stage catalog', () => {
|
|
1416
|
+
const result = UpdateCompanyRequestSchema.safeParse({
|
|
1417
|
+
processingState: {
|
|
1418
|
+
[LEAD_GEN_STAGE_CATALOG.qualified.key]: {
|
|
1419
|
+
status: 'success',
|
|
1420
|
+
data: { score: 92 }
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
})
|
|
1424
|
+
|
|
1425
|
+
expect(result.success).toBe(true)
|
|
1426
|
+
})
|
|
1427
|
+
|
|
1428
|
+
it('accepts deprecated pipelineStatus as a compatibility no-op', () => {
|
|
1429
|
+
expect(UpdateCompanyRequestSchema.safeParse({ pipelineStatus: 'emailed' }).success).toBe(true)
|
|
1430
|
+
})
|
|
1431
|
+
|
|
1432
|
+
it('rejects processingState keys outside the stage catalog', () => {
|
|
1433
|
+
expect(
|
|
1434
|
+
UpdateCompanyRequestSchema.safeParse({
|
|
1435
|
+
processingState: {
|
|
1436
|
+
madeUpStage: { status: 'success' }
|
|
1437
|
+
}
|
|
1438
|
+
}).success
|
|
1439
|
+
).toBe(false)
|
|
1440
|
+
})
|
|
1441
|
+
|
|
1258
1442
|
it('accepts numEmployees of 0', () => {
|
|
1259
1443
|
expect(UpdateCompanyRequestSchema.safeParse({ numEmployees: 0 }).success).toBe(true)
|
|
1260
1444
|
})
|
|
@@ -1468,7 +1652,7 @@ describe('AcqContactResponseSchema (forward-compat)', () => {
|
|
|
1468
1652
|
openingLine: null,
|
|
1469
1653
|
source: null,
|
|
1470
1654
|
sourceId: null,
|
|
1471
|
-
|
|
1655
|
+
processingState: null,
|
|
1472
1656
|
enrichmentData: null,
|
|
1473
1657
|
attioPersonId: null,
|
|
1474
1658
|
batchId: null,
|