@coldiq/mcp 0.1.13 → 0.1.15
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/package.json
CHANGED
package/src/executor.ts
CHANGED
|
@@ -128,6 +128,17 @@ function sleep(ms: number): Promise<void> {
|
|
|
128
128
|
// Execute a single provider
|
|
129
129
|
// ---------------------------------------------------------------------------
|
|
130
130
|
|
|
131
|
+
// Per-provider sync wall-clock cap. Most providers return "no result" in <5s when
|
|
132
|
+
// they have no match; a slow provider returning empty late only delays the next
|
|
133
|
+
// fallback. Capping keeps a 9-provider waterfall walk bounded — at most ~90s end-
|
|
134
|
+
// to-end when every provider misses, vs. multi-minute stalls observed in production
|
|
135
|
+
// before this cap. Override via COLDIQ_SYNC_PROVIDER_TIMEOUT_MS for debugging/tests.
|
|
136
|
+
// Read each call so tests can adjust without module reloads. Does NOT apply to
|
|
137
|
+
// async providers — their own pollIntervalMs/timeoutMs govern.
|
|
138
|
+
function syncProviderTimeoutMs(): number {
|
|
139
|
+
return parseInt(process.env.COLDIQ_SYNC_PROVIDER_TIMEOUT_MS ?? '10000', 10)
|
|
140
|
+
}
|
|
141
|
+
|
|
131
142
|
async function executeSingle(
|
|
132
143
|
provider: ProviderEntry,
|
|
133
144
|
input: Record<string, unknown>,
|
|
@@ -151,7 +162,20 @@ async function executeSingle(
|
|
|
151
162
|
const asyncResult = await executeAsync(provider, payload)
|
|
152
163
|
result = { ...asyncResult, status: asyncResult.ok ? 200 : 500 }
|
|
153
164
|
} else {
|
|
154
|
-
|
|
165
|
+
const cap = syncProviderTimeoutMs()
|
|
166
|
+
const call = callApi(provider.method, provider.endpoint, payload.body, payload.queryParams)
|
|
167
|
+
const timeout = new Promise<{ ok: boolean; status: number; data: unknown }>((resolve) =>
|
|
168
|
+
setTimeout(
|
|
169
|
+
() =>
|
|
170
|
+
resolve({
|
|
171
|
+
ok: false,
|
|
172
|
+
status: 0,
|
|
173
|
+
data: { error: `Provider exceeded ${cap}ms sync cap` },
|
|
174
|
+
}),
|
|
175
|
+
cap,
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
result = await Promise.race([call, timeout])
|
|
155
179
|
}
|
|
156
180
|
|
|
157
181
|
return { ...result, latencyMs: Date.now() - start }
|
package/src/registry.ts
CHANGED
|
@@ -1045,7 +1045,9 @@ const findPeopleProviders: ProviderEntry[] = [
|
|
|
1045
1045
|
},
|
|
1046
1046
|
extractId: (response) => {
|
|
1047
1047
|
const d = response as Record<string, unknown>
|
|
1048
|
-
|
|
1048
|
+
// LeadsFactory's upstream returns the ObjectId as `id`; the short `search_id`
|
|
1049
|
+
// cannot be used as the GET param (upstream rejects it as "not a valid ObjectId").
|
|
1050
|
+
return (d.id as string) ?? (d.search_id as string)
|
|
1049
1051
|
},
|
|
1050
1052
|
},
|
|
1051
1053
|
},
|
|
@@ -1068,6 +1070,38 @@ const findPeopleProviders: ProviderEntry[] = [
|
|
|
1068
1070
|
const d = data as Record<string, unknown>
|
|
1069
1071
|
return isNonEmptyArray(d.people) || isNonEmptyArray(d.contacts)
|
|
1070
1072
|
},
|
|
1073
|
+
// Apollo's `q_organization_domains` is a soft hint — when titles match strongly,
|
|
1074
|
+
// Apollo returns people from other companies whose title fits (e.g. a CEO seller
|
|
1075
|
+
// on the Hotmart platform, or a random Brazilian CEO when filtering by WeWork).
|
|
1076
|
+
// Strict-filter post-hoc by the person's actual employer domain when the caller
|
|
1077
|
+
// supplied company identifiers.
|
|
1078
|
+
postFilter: (data, input) => {
|
|
1079
|
+
const wantDomains = ((input.company_domains as string[] | undefined) ?? [])
|
|
1080
|
+
.map((d) => d.toLowerCase().replace(/^https?:\/\//, '').replace(/^www\./, '').split('/')[0])
|
|
1081
|
+
.filter(Boolean)
|
|
1082
|
+
const wantLinkedIns = ((input.company_linkedin_urls as string[] | undefined) ?? [])
|
|
1083
|
+
.map((u) => u.toLowerCase().replace(/^https?:\/\//, '').replace(/^www\./, '').replace(/\/+$/, ''))
|
|
1084
|
+
.filter(Boolean)
|
|
1085
|
+
if (wantDomains.length === 0 && wantLinkedIns.length === 0) return data
|
|
1086
|
+
const d = { ...(data as Record<string, unknown>) }
|
|
1087
|
+
const people = (d.people as Array<Record<string, unknown>> | undefined) ?? []
|
|
1088
|
+
d.people = people.filter((p) => {
|
|
1089
|
+
const org = p.organization as Record<string, unknown> | undefined
|
|
1090
|
+
const orgWebsite = (org?.website_url as string | undefined)?.toLowerCase()
|
|
1091
|
+
.replace(/^https?:\/\//, '').replace(/^www\./, '').split('/')[0]
|
|
1092
|
+
const orgLinkedIn = (org?.linkedin_url as string | undefined)?.toLowerCase()
|
|
1093
|
+
.replace(/^https?:\/\//, '').replace(/^www\./, '').replace(/\/+$/, '')
|
|
1094
|
+
// Keep only if the person's employer matches one of the requested companies.
|
|
1095
|
+
// Lenient when the field is missing — providers occasionally omit org details
|
|
1096
|
+
// but the person is still a match (e.g. found via name+title lookup).
|
|
1097
|
+
if (orgWebsite && wantDomains.length > 0 && wantDomains.includes(orgWebsite)) return true
|
|
1098
|
+
if (orgLinkedIn && wantLinkedIns.length > 0 && wantLinkedIns.some((u) => orgLinkedIn === u)) return true
|
|
1099
|
+
// If we have org info but neither matches, drop. If we have no org info at all, drop too —
|
|
1100
|
+
// safer to lose an unverified record than to include a false positive.
|
|
1101
|
+
return false
|
|
1102
|
+
})
|
|
1103
|
+
return d
|
|
1104
|
+
},
|
|
1071
1105
|
},
|
|
1072
1106
|
{
|
|
1073
1107
|
id: 'pdl',
|
package/tests/executor.test.ts
CHANGED
|
@@ -738,3 +738,54 @@ describe('executeWithFallback with options.providers', () => {
|
|
|
738
738
|
|
|
739
739
|
// Note: the LeadsFactory backoff *schedule* itself is asserted against the live
|
|
740
740
|
// provider entry in tests/registry.test.ts to avoid drift between test and source.
|
|
741
|
+
|
|
742
|
+
// ---------------------------------------------------------------------------
|
|
743
|
+
// Per-provider sync timeout cap — keeps a missing-everywhere waterfall bounded
|
|
744
|
+
// ---------------------------------------------------------------------------
|
|
745
|
+
|
|
746
|
+
describe('per-provider sync timeout cap', () => {
|
|
747
|
+
const originalFetch = globalThis.fetch
|
|
748
|
+
const originalEnv = process.env.COLDIQ_SYNC_PROVIDER_TIMEOUT_MS
|
|
749
|
+
|
|
750
|
+
beforeEach(() => {
|
|
751
|
+
initClient('http://test-api.local', 'test-key-123')
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
afterEach(() => {
|
|
755
|
+
globalThis.fetch = originalFetch
|
|
756
|
+
vi.restoreAllMocks()
|
|
757
|
+
if (originalEnv === undefined) delete process.env.COLDIQ_SYNC_PROVIDER_TIMEOUT_MS
|
|
758
|
+
else process.env.COLDIQ_SYNC_PROVIDER_TIMEOUT_MS = originalEnv
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
it('caps a hanging sync provider and falls through to the next', async () => {
|
|
762
|
+
process.env.COLDIQ_SYNC_PROVIDER_TIMEOUT_MS = '120'
|
|
763
|
+
stubProviders([
|
|
764
|
+
makeProvider({ id: 'slow', priority: 1, hasResult: () => true }),
|
|
765
|
+
makeProvider({ id: 'fast', priority: 2, hasResult: (d) => (d as { ok?: boolean }).ok === true }),
|
|
766
|
+
])
|
|
767
|
+
|
|
768
|
+
let slowCalled = false
|
|
769
|
+
let fastCalled = false
|
|
770
|
+
globalThis.fetch = vi.fn(async () => {
|
|
771
|
+
if (!slowCalled) {
|
|
772
|
+
slowCalled = true
|
|
773
|
+
await new Promise((r) => setTimeout(r, 5000))
|
|
774
|
+
return new Response(JSON.stringify({ ok: true }), { status: 200 })
|
|
775
|
+
}
|
|
776
|
+
fastCalled = true
|
|
777
|
+
return new Response(JSON.stringify({ ok: true }), { status: 200 })
|
|
778
|
+
}) as typeof fetch
|
|
779
|
+
|
|
780
|
+
const start = Date.now()
|
|
781
|
+
const result = await executeWithFallback('enrich_company', { domain: 'coldiq.com' })
|
|
782
|
+
const elapsed = Date.now() - start
|
|
783
|
+
|
|
784
|
+
expect(slowCalled).toBe(true)
|
|
785
|
+
expect(fastCalled).toBe(true)
|
|
786
|
+
expect('data' in result).toBe(true)
|
|
787
|
+
if ('data' in result) expect(result._meta.provider).toBe('fast')
|
|
788
|
+
// Slow attempt should give up around 120ms, total well under the 5s hang.
|
|
789
|
+
expect(elapsed).toBeLessThan(2000)
|
|
790
|
+
})
|
|
791
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Direct upstream probe — bypasses the marketplace proxy. Goal: figure out why
|
|
2
|
+
// GET /api/v1/find-people/searches/{id} returns errors from our proxy. Probe with
|
|
3
|
+
// both id formats (mongo _id and short search_id) and both path forms (singular vs plural).
|
|
4
|
+
//
|
|
5
|
+
// Run: LEADSFACTORY_API_KEY=… npx tsx mcp/tests/live/leadsfactory-upstream-probe.ts
|
|
6
|
+
|
|
7
|
+
const KEY = process.env.LEADSFACTORY_API_KEY
|
|
8
|
+
if (!KEY) { console.error('LEADSFACTORY_API_KEY required'); process.exit(1) }
|
|
9
|
+
const BASE = 'https://apiv2.leadsfactory.io'
|
|
10
|
+
|
|
11
|
+
async function call(method: 'GET' | 'POST', path: string, body?: unknown) {
|
|
12
|
+
const t0 = Date.now()
|
|
13
|
+
try {
|
|
14
|
+
const res = await fetch(`${BASE}${path}`, {
|
|
15
|
+
method,
|
|
16
|
+
headers: {
|
|
17
|
+
'X-API-Key': KEY!,
|
|
18
|
+
...(body ? { 'Content-Type': 'application/json' } : {}),
|
|
19
|
+
},
|
|
20
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
21
|
+
signal: AbortSignal.timeout(30_000),
|
|
22
|
+
})
|
|
23
|
+
let data: any
|
|
24
|
+
try { data = await res.json() } catch { data = await res.text().catch(() => '<no body>') }
|
|
25
|
+
return { ok: res.ok, status: res.status, ms: Date.now() - t0, data }
|
|
26
|
+
} catch (err) {
|
|
27
|
+
return { ok: false, status: 0, ms: Date.now() - t0, data: { error: String(err) } }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function summary(r: any, max = 300) {
|
|
32
|
+
const s = typeof r.data === 'string' ? r.data : JSON.stringify(r.data)
|
|
33
|
+
return `status=${r.status} ms=${r.ms} body=${s.slice(0, max)}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function main() {
|
|
37
|
+
console.log('=== LeadsFactory upstream probe ===')
|
|
38
|
+
console.log(`BASE=${BASE}\n`)
|
|
39
|
+
|
|
40
|
+
// 1. Create a small search to get fresh IDs to test against
|
|
41
|
+
console.log('▸ Step 1: POST to create a small search (1 contact at ColdIQ)')
|
|
42
|
+
const post = await call('POST', '/api/v1/find-people/search', {
|
|
43
|
+
company_domains: ['coldiq.com'],
|
|
44
|
+
search: {
|
|
45
|
+
max_persona_results: 1,
|
|
46
|
+
personas: [{ job_title: 'CEO' }],
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
console.log(` ${summary(post, 800)}`)
|
|
50
|
+
|
|
51
|
+
if (!post.ok) {
|
|
52
|
+
console.log(' POST failed; aborting GET probes.')
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const mongoId = post.data?._id
|
|
57
|
+
const searchId = post.data?.search_id
|
|
58
|
+
console.log(`\n mongoId=${mongoId}`)
|
|
59
|
+
console.log(` searchId=${searchId}\n`)
|
|
60
|
+
|
|
61
|
+
// 2. Try the GET in every shape we can think of
|
|
62
|
+
console.log('▸ Step 2: GET probes (try every path/id combo)')
|
|
63
|
+
|
|
64
|
+
for (const id of [mongoId, searchId].filter(Boolean)) {
|
|
65
|
+
const idType = id === mongoId ? 'mongoId' : 'searchId'
|
|
66
|
+
for (const path of [
|
|
67
|
+
`/api/v1/find-people/searches/${id}`, // current marketplace shape (plural)
|
|
68
|
+
`/api/v1/find-people/search/${id}`, // singular — matches POST path style
|
|
69
|
+
`/api/v1/find-people/searches/${id}/`, // trailing slash
|
|
70
|
+
`/api/v1/find-people/${id}`, // no segment
|
|
71
|
+
`/api/v1/find-people/search?id=${id}`, // query param
|
|
72
|
+
`/api/v1/find-people/search?search_id=${id}`,
|
|
73
|
+
]) {
|
|
74
|
+
const r = await call('GET', path)
|
|
75
|
+
console.log(` [${idType}] GET ${path}`)
|
|
76
|
+
console.log(` ${summary(r)}`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3. Try the GET via different auth shapes (in case X-API-Key vs Bearer matters for GET)
|
|
81
|
+
console.log('\n▸ Step 3: alternate auth headers on the canonical path (plural + mongoId)')
|
|
82
|
+
const probePath = `/api/v1/find-people/searches/${mongoId}`
|
|
83
|
+
for (const [label, headers] of [
|
|
84
|
+
['X-API-Key', { 'X-API-Key': KEY! }],
|
|
85
|
+
['Authorization Bearer', { Authorization: `Bearer ${KEY}` }],
|
|
86
|
+
['Authorization raw', { Authorization: KEY! }],
|
|
87
|
+
['Api-Key', { 'Api-Key': KEY! }],
|
|
88
|
+
] as const) {
|
|
89
|
+
const t0 = Date.now()
|
|
90
|
+
const res = await fetch(`${BASE}${probePath}`, { headers, signal: AbortSignal.timeout(20_000) })
|
|
91
|
+
let body: any
|
|
92
|
+
try { body = await res.json() } catch { body = await res.text() }
|
|
93
|
+
console.log(` [${label}] status=${res.status} ms=${Date.now() - t0} body=${JSON.stringify(body).slice(0, 200)}`)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main().catch((e) => { console.error('FATAL', e); process.exit(1) })
|
|
@@ -362,6 +362,72 @@ describe('apollo (find_people)', () => {
|
|
|
362
362
|
const body = result.body as Record<string, unknown>
|
|
363
363
|
expect(body.q_keywords).toBeUndefined()
|
|
364
364
|
})
|
|
365
|
+
|
|
366
|
+
it('postFilter drops people whose employer does not match company_domains', () => {
|
|
367
|
+
const filtered = p().postFilter!(
|
|
368
|
+
{
|
|
369
|
+
people: [
|
|
370
|
+
{ name: 'Real WeWork CEO', organization: { website_url: 'https://wework.com/about' } },
|
|
371
|
+
{ name: 'Random Brazilian CEO', organization: { website_url: 'shiftcompany.com' } },
|
|
372
|
+
{ name: 'Hotmart Seller', organization: { website_url: 'someseller.com.br' } },
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
{ company_domains: ['wework.com'], job_titles: ['CEO'] },
|
|
376
|
+
) as { people: Array<{ name: string }> }
|
|
377
|
+
expect(filtered.people.map((p) => p.name)).toEqual(['Real WeWork CEO'])
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
it('postFilter handles `www.` and protocol prefixes when matching', () => {
|
|
381
|
+
const filtered = p().postFilter!(
|
|
382
|
+
{
|
|
383
|
+
people: [
|
|
384
|
+
{ name: 'Match A', organization: { website_url: 'https://www.coldiq.com/' } },
|
|
385
|
+
{ name: 'Match B', organization: { website_url: 'coldiq.com' } },
|
|
386
|
+
{ name: 'No match', organization: { website_url: 'otherco.com' } },
|
|
387
|
+
],
|
|
388
|
+
},
|
|
389
|
+
{ company_domains: ['coldiq.com'] },
|
|
390
|
+
) as { people: Array<{ name: string }> }
|
|
391
|
+
expect(filtered.people.map((p) => p.name)).toEqual(['Match A', 'Match B'])
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it('postFilter matches by LinkedIn URL when company_linkedin_urls is provided', () => {
|
|
395
|
+
const filtered = p().postFilter!(
|
|
396
|
+
{
|
|
397
|
+
people: [
|
|
398
|
+
{ name: 'WW employee', organization: { linkedin_url: 'https://linkedin.com/company/wework' } },
|
|
399
|
+
{ name: 'WW employee normalized', organization: { linkedin_url: 'linkedin.com/company/wework' } },
|
|
400
|
+
{ name: 'Other', organization: { linkedin_url: 'linkedin.com/company/other' } },
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
{ company_linkedin_urls: ['https://linkedin.com/company/wework'] },
|
|
404
|
+
) as { people: Array<{ name: string }> }
|
|
405
|
+
expect(filtered.people.map((p) => p.name)).toEqual(['WW employee', 'WW employee normalized'])
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it('postFilter passes data through unchanged when no company filter is requested', () => {
|
|
409
|
+
const input = {
|
|
410
|
+
people: [
|
|
411
|
+
{ name: 'A', organization: { website_url: 'a.com' } },
|
|
412
|
+
{ name: 'B', organization: { website_url: 'b.com' } },
|
|
413
|
+
],
|
|
414
|
+
}
|
|
415
|
+
const out = p().postFilter!(input, { job_titles: ['CEO'] }) as typeof input
|
|
416
|
+
expect(out.people.map((p) => p.name)).toEqual(['A', 'B'])
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('postFilter drops records that are missing organization info entirely (safer to lose than to risk false positives)', () => {
|
|
420
|
+
const filtered = p().postFilter!(
|
|
421
|
+
{
|
|
422
|
+
people: [
|
|
423
|
+
{ name: 'Has org', organization: { website_url: 'wework.com' } },
|
|
424
|
+
{ name: 'No org' }, // missing organization
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
{ company_domains: ['wework.com'] },
|
|
428
|
+
) as { people: Array<{ name: string }> }
|
|
429
|
+
expect(filtered.people.map((p) => p.name)).toEqual(['Has org'])
|
|
430
|
+
})
|
|
365
431
|
})
|
|
366
432
|
|
|
367
433
|
// ---------------------------------------------------------------------------
|
|
@@ -21,7 +21,7 @@ describe('find_people handler', () => {
|
|
|
21
21
|
|
|
22
22
|
// LeadsFactory create search — returns an ID
|
|
23
23
|
if (urlStr.includes('/leadsfactory/contact-finder/searches') && callCount === 1) {
|
|
24
|
-
return new Response(JSON.stringify({
|
|
24
|
+
return new Response(JSON.stringify({ id: 'abc123', search_id: 'abc', nb_jobs_total: 1, progress_percentage: 0 }), { status: 201 })
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// LeadsFactory poll — always RUNNING (will timeout)
|
|
@@ -31,7 +31,7 @@ describe('find_people handler', () => {
|
|
|
31
31
|
|
|
32
32
|
// Apollo — succeeds
|
|
33
33
|
if (urlStr.includes('/apollo/people/search')) {
|
|
34
|
-
return new Response(JSON.stringify({ people: [{ name: 'Michel Lieben', title: 'CEO' }] }), { status: 200 })
|
|
34
|
+
return new Response(JSON.stringify({ people: [{ name: 'Michel Lieben', title: 'CEO', organization: { website_url: 'coldiq.com' } }] }), { status: 200 })
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 })
|
|
@@ -70,7 +70,7 @@ describe('find_people handler', () => {
|
|
|
70
70
|
|
|
71
71
|
// LeadsFactory create search
|
|
72
72
|
if (urlStr.includes('/leadsfactory/contact-finder/searches') && !urlStr.includes('abc123')) {
|
|
73
|
-
return new Response(JSON.stringify({
|
|
73
|
+
return new Response(JSON.stringify({ id: 'abc123', search_id: 'abc', nb_jobs_total: 1, progress_percentage: 0 }), { status: 201 })
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// LeadsFactory poll — first RUNNING, then SUCCESSFUL with contacts
|
|
@@ -121,7 +121,7 @@ describe('find_people handler', () => {
|
|
|
121
121
|
|
|
122
122
|
// LeadsFactory create
|
|
123
123
|
if (urlStr.includes('/leadsfactory/contact-finder/searches') && !urlStr.includes('abc123')) {
|
|
124
|
-
return new Response(JSON.stringify({
|
|
124
|
+
return new Response(JSON.stringify({ id: 'abc123', search_id: 'abc', nb_jobs_total: 2, progress_percentage: 0 }), { status: 201 })
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
// LeadsFactory poll — SUCCESSFUL with one miss
|
|
@@ -138,7 +138,7 @@ describe('find_people handler', () => {
|
|
|
138
138
|
if (urlStr.includes('/apollo/people/search')) {
|
|
139
139
|
const body = JSON.parse((opts?.body as string) ?? '{}')
|
|
140
140
|
if (body.q_organization_domains?.includes('folk.app')) {
|
|
141
|
-
return new Response(JSON.stringify({ people: [{ name: 'Thibaud Elziere', title: 'CEO' }] }), { status: 200 })
|
|
141
|
+
return new Response(JSON.stringify({ people: [{ name: 'Thibaud Elziere', title: 'CEO', organization: { website_url: 'folk.app' } }] }), { status: 200 })
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -179,7 +179,7 @@ describe('find_people handler', () => {
|
|
|
179
179
|
const urlStr = url.toString()
|
|
180
180
|
|
|
181
181
|
if (urlStr.includes('/leadsfactory/contact-finder/searches') && !urlStr.includes('abc123')) {
|
|
182
|
-
return new Response(JSON.stringify({
|
|
182
|
+
return new Response(JSON.stringify({ id: 'abc123', nb_jobs_total: 2, progress_percentage: 0 }), { status: 201 })
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
if (urlStr.includes('/leadsfactory/contact-finder/searches/abc123')) {
|
|
@@ -231,7 +231,7 @@ describe('find_people handler', () => {
|
|
|
231
231
|
|
|
232
232
|
if (urlStr.includes('/leadsfactory/contact-finder/searches') && !urlStr.includes('abc123')) {
|
|
233
233
|
capturedBody = JSON.parse((opts?.body as string) ?? '{}')
|
|
234
|
-
return new Response(JSON.stringify({
|
|
234
|
+
return new Response(JSON.stringify({ id: 'abc123', nb_jobs_total: 2, progress_percentage: 0 }), { status: 201 })
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
if (urlStr.includes('/leadsfactory/contact-finder/searches/abc123')) {
|
|
@@ -277,7 +277,7 @@ describe('find_people handler', () => {
|
|
|
277
277
|
|
|
278
278
|
if (urlStr.includes('/leadsfactory/contact-finder/searches') && !urlStr.includes('abc123')) {
|
|
279
279
|
capturedBody = JSON.parse((opts?.body as string) ?? '{}')
|
|
280
|
-
return new Response(JSON.stringify({
|
|
280
|
+
return new Response(JSON.stringify({ id: 'abc123', nb_jobs_total: 1, progress_percentage: 0 }), { status: 201 })
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
if (urlStr.includes('/leadsfactory/contact-finder/searches/abc123')) {
|
|
@@ -325,7 +325,7 @@ describe('find_people handler', () => {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
if (urlStr.includes('/apollo/people/search')) {
|
|
328
|
-
return new Response(JSON.stringify({ people: [{ name: 'Michel Lieben' }] }), { status: 200 })
|
|
328
|
+
return new Response(JSON.stringify({ people: [{ name: 'Michel Lieben', organization: { website_url: 'coldiq.com' } }] }), { status: 200 })
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 })
|