@coldiq/mcp 0.4.3 → 0.4.5
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.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +26 -102
- package/dist/registry.js.map +1 -1
- package/dist/tools/cancel-bulk-job.d.ts +14 -0
- package/dist/tools/cancel-bulk-job.d.ts.map +1 -0
- package/dist/tools/cancel-bulk-job.js +18 -0
- package/dist/tools/cancel-bulk-job.js.map +1 -0
- package/dist/tools/enrich-company-bulk.d.ts.map +1 -1
- package/dist/tools/enrich-company-bulk.js +2 -1
- package/dist/tools/enrich-company-bulk.js.map +1 -1
- package/dist/tools/enrich-person-bulk.d.ts.map +1 -1
- package/dist/tools/enrich-person-bulk.js +2 -1
- package/dist/tools/enrich-person-bulk.js.map +1 -1
- package/dist/tools/find-emails-bulk.d.ts.map +1 -1
- package/dist/tools/find-emails-bulk.js +2 -1
- package/dist/tools/find-emails-bulk.js.map +1 -1
- package/dist/tools/find-phone-bulk.d.ts.map +1 -1
- package/dist/tools/find-phone-bulk.js +2 -1
- package/dist/tools/find-phone-bulk.js.map +1 -1
- package/dist/tools/get-bulk-job.d.ts.map +1 -1
- package/dist/tools/get-bulk-job.js +3 -2
- package/dist/tools/get-bulk-job.js.map +1 -1
- package/dist/utils/provider-resolver.d.ts.map +1 -1
- package/dist/utils/provider-resolver.js +4 -10
- package/dist/utils/provider-resolver.js.map +1 -1
- package/dist/utils/selection-insight.d.ts.map +1 -1
- package/dist/utils/selection-insight.js +6 -8
- package/dist/utils/selection-insight.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/registry.ts +27 -98
- package/src/tools/cancel-bulk-job.ts +22 -0
- package/src/tools/enrich-company-bulk.ts +2 -1
- package/src/tools/enrich-person-bulk.ts +2 -1
- package/src/tools/find-emails-bulk.ts +2 -1
- package/src/tools/find-phone-bulk.ts +2 -1
- package/src/tools/get-bulk-job.ts +3 -2
- package/src/utils/provider-resolver.ts +4 -10
- package/src/utils/selection-insight.ts +6 -8
- package/tests/bulk-tools.test.ts +17 -0
- package/tests/provider-availability.test.ts +4 -4
- package/tests/registry-polling.test.ts +4 -4
- package/tests/registry.test.ts +32 -81
- package/tests/tools/list-data-sources.test.ts +1 -1
- package/tests/utils/provider-resolver.test.ts +7 -7
- package/tests/utils/selection-insight.test.ts +2 -2
|
@@ -7,7 +7,8 @@ export const enrichCompanyBulkDescription =
|
|
|
7
7
|
'Enrich up to 5,000 companies (size, industry, funding, technologies, social profiles) in one ASYNC job. Returns a job_id immediately — this does NOT return firmographics inline. ' +
|
|
8
8
|
'Poll get_bulk_job(job_id) until status is "done" (drains over a few minutes), then page through rows. ' +
|
|
9
9
|
'Each row runs the same managed waterfall as enrich_company; provide at least one of domain, linkedin_url, or name per company. ' +
|
|
10
|
-
'Charged only per row a provider enriches (misses are free). For a single company, use enrich_company instead.'
|
|
10
|
+
'Charged only per row a provider enriches (misses are free). For a single company, use enrich_company instead. ' +
|
|
11
|
+
'Call cancel_bulk_job to stop a running job early — already-processed rows stay charged, un-started rows are never billed.'
|
|
11
12
|
|
|
12
13
|
export const enrichCompanyBulkSchema = {
|
|
13
14
|
inputs: z
|
|
@@ -7,7 +7,8 @@ export const enrichPersonBulkDescription =
|
|
|
7
7
|
'Enrich up to 5,000 people (LinkedIn profile + professional details) in one ASYNC job. Returns a job_id immediately — this does NOT return profiles inline. ' +
|
|
8
8
|
'Poll get_bulk_job(job_id) until status is "done" (drains over a few minutes), then page through rows. ' +
|
|
9
9
|
'Each row runs the same managed waterfall as enrich_person; provide an email, a linkedin_url, or name + company/domain per person. ' +
|
|
10
|
-
'Charged only per row a provider enriches (misses are free). For a single person, use enrich_person instead.'
|
|
10
|
+
'Charged only per row a provider enriches (misses are free). For a single person, use enrich_person instead. ' +
|
|
11
|
+
'Call cancel_bulk_job to stop a running job early — already-processed rows stay charged, un-started rows are never billed.'
|
|
11
12
|
|
|
12
13
|
export const enrichPersonBulkSchema = {
|
|
13
14
|
inputs: z
|
|
@@ -13,7 +13,8 @@ export const findEmailsBulkDescription =
|
|
|
13
13
|
'result carries a `confidence` (high/medium/unknown) — treat medium/unknown as unverified and run ' +
|
|
14
14
|
'verify_emails_bulk before sending, or you risk bounces. A located email the provider itself flags as a low-confidence ' +
|
|
15
15
|
'pattern guess comes back as verdict `guessed` instead of `found` and is FREE — never send a `guessed` email unverified. ' +
|
|
16
|
-
'For 50 or fewer people that you want back inline, use find_emails instead.'
|
|
16
|
+
'For 50 or fewer people that you want back inline, use find_emails instead. ' +
|
|
17
|
+
'Started a job that is too big or wrong? Call cancel_bulk_job to stop it — already-processed rows stay charged, un-started rows are never billed.'
|
|
17
18
|
|
|
18
19
|
export const findEmailsBulkSchema = {
|
|
19
20
|
people: z
|
|
@@ -7,7 +7,8 @@ export const findPhoneBulkDescription =
|
|
|
7
7
|
'Find direct/mobile phone numbers for up to 5,000 people in one ASYNC job. Returns a job_id immediately — this does NOT return numbers inline. ' +
|
|
8
8
|
'Poll get_bulk_job(job_id) until status is "done" (drains over a few minutes), then page through rows. ' +
|
|
9
9
|
'Each row runs the same managed waterfall as find_phone; provide a linkedin_url, or first+last name with company_domain/company_name. ' +
|
|
10
|
-
'Charged only per row a provider returns a number (misses are free). Phone lookups are relatively expensive — scope the list deliberately. For a single person, use find_phone instead.'
|
|
10
|
+
'Charged only per row a provider returns a number (misses are free). Phone lookups are relatively expensive — scope the list deliberately. For a single person, use find_phone instead. ' +
|
|
11
|
+
'Overshot the list? Call cancel_bulk_job to stop it immediately — already-processed rows stay charged, un-started rows are never billed.'
|
|
11
12
|
|
|
12
13
|
export const findPhoneBulkSchema = {
|
|
13
14
|
inputs: z
|
|
@@ -5,14 +5,15 @@ export const getBulkJobName = 'get_bulk_job'
|
|
|
5
5
|
|
|
6
6
|
export const getBulkJobDescription =
|
|
7
7
|
'Poll any async bulk job (verify_emails_bulk, find_emails_bulk, enrich_person_bulk, enrich_company_bulk, find_phone_bulk). ' +
|
|
8
|
-
'Returns { status, items_total, items_processed, summary, coverage, credits_reserved, credits_charged, page }. Poll until status is "done"
|
|
8
|
+
'Returns { status, items_total, items_processed, summary, coverage, credits_reserved, credits_charged, page }. Poll until status is TERMINAL — one of "done", "timed_out", "failed", or "cancelled" (a job someone stopped with cancel_bulk_job); stop polling on any of these. ' +
|
|
9
9
|
'Use the `summary` per-verdict counts for totals — do NOT expect all rows inline: rows come one page at a time (default 100). ' +
|
|
10
10
|
'For find jobs, verdict `guessed` means an email was located but the provider marked it a low-confidence pattern guess — it is NOT billed; verify it before sending. ' +
|
|
11
11
|
'The `coverage` block {conclusive, skipped, unverifiable, failed, pending} tells you how much was truly resolved: `skipped` rows (per-row ' +
|
|
12
12
|
'`result.reason` chunk_failed/not_returned/timed_out) were never conclusively checked and are safe to re-submit; `failed` is a hard error (retry unlikely to help). ' +
|
|
13
13
|
'`credits_charged` (null until settled) is the actual spend; `credits_reserved` is the worst-case hold. Each row echoes its submitted `input`, ' +
|
|
14
14
|
'so you can map verdicts back to your submissions without tracking order. To page, OMIT cursor for the first page, then call again with ' +
|
|
15
|
-
'cursor = page.next_cursor until next_cursor is null.'
|
|
15
|
+
'cursor = page.next_cursor until next_cursor is null. ' +
|
|
16
|
+
'To STOP a running find/enrich/phone job early (before it spends more credits), call cancel_bulk_job — already-processed rows stay charged, un-started rows are never billed.'
|
|
16
17
|
|
|
17
18
|
export const getBulkJobSchema = {
|
|
18
19
|
job_id: z.number().int().describe('The job_id returned by a *_bulk submit tool.'),
|
|
@@ -8,16 +8,12 @@ import { fuzzyMatch } from './fuzzy.js'
|
|
|
8
8
|
// Order = auto-route execution order: bulk providers first (Steps 1-3), then
|
|
9
9
|
// the single-find_email fallback providers used for stragglers (Step 4).
|
|
10
10
|
export const CONFIGURED_FIND_EMAILS_PROVIDERS = [
|
|
11
|
-
'
|
|
11
|
+
'findymail',
|
|
12
12
|
'leadmagic',
|
|
13
13
|
'prospeo',
|
|
14
|
-
'
|
|
15
|
-
'limadata-work-email-linkedin',
|
|
14
|
+
'wiza',
|
|
16
15
|
'apollo-people-match',
|
|
17
|
-
'
|
|
18
|
-
'fullenrich',
|
|
19
|
-
'pdl-person-enrich',
|
|
20
|
-
'linkupapi',
|
|
16
|
+
'limadata-work-email',
|
|
21
17
|
'icypeas',
|
|
22
18
|
]
|
|
23
19
|
|
|
@@ -223,10 +219,8 @@ const GATED_DESCRIPTIONS: Partial<Record<Capability, Partial<Record<string, Gate
|
|
|
223
219
|
find_email: {
|
|
224
220
|
findymail: { kind: 'requires', fields: 'first_name and (domain or company_name)' },
|
|
225
221
|
'limadata-work-email': { kind: 'requires', fields: 'first_name, last_name, and domain' },
|
|
226
|
-
|
|
227
|
-
'limadata-work-email-linkedin': { kind: 'requires', fields: 'linkedin_url' },
|
|
222
|
+
wiza: { kind: 'requires', fields: 'linkedin_url or (first_name, last_name, and domain/company_name)' },
|
|
228
223
|
'apollo-people-match': { kind: 'requires', fields: 'linkedin_url or (first_name, last_name, and domain/company_name)' },
|
|
229
|
-
'pdl-person-enrich': { kind: 'requires', fields: 'linkedin_url or (first_name, last_name, and domain/company_name)' },
|
|
230
224
|
},
|
|
231
225
|
// -------------------------------------------------------------------------
|
|
232
226
|
verify_email: {},
|
|
@@ -73,23 +73,21 @@ const STRENGTHS: Partial<Record<InsightCapability, Record<string, string>>> = {
|
|
|
73
73
|
},
|
|
74
74
|
find_email: {
|
|
75
75
|
prospeo: 'strong work-email coverage',
|
|
76
|
-
fullenrich: 'strong international email coverage, especially across Europe',
|
|
77
76
|
findymail: 'high-accuracy verified work emails',
|
|
77
|
+
wiza: 'live SMTP-verified email reveals (premium)',
|
|
78
78
|
icypeas: 'broad email-finding coverage',
|
|
79
79
|
'limadata-work-email': 'name-and-domain work-email lookup',
|
|
80
|
-
|
|
81
|
-
'
|
|
82
|
-
linkupapi: 'LinkedIn-sourced email finding',
|
|
80
|
+
leadmagic: 'pay-per-result email finding with employment verification',
|
|
81
|
+
'apollo-people-match': 'person-matched current-employer emails (recovers job movers)',
|
|
83
82
|
},
|
|
84
83
|
find_emails: {
|
|
85
84
|
prospeo: 'strong work-email coverage',
|
|
86
|
-
fullenrich: 'strong international email coverage, especially across Europe',
|
|
87
85
|
findymail: 'high-accuracy verified work emails',
|
|
86
|
+
wiza: 'live SMTP-verified email reveals (premium)',
|
|
88
87
|
icypeas: 'broad email-finding coverage',
|
|
89
88
|
'limadata-work-email': 'name-and-domain work-email lookup',
|
|
90
|
-
|
|
91
|
-
'
|
|
92
|
-
linkupapi: 'LinkedIn-sourced email finding',
|
|
89
|
+
leadmagic: 'pay-per-result email finding with employment verification',
|
|
90
|
+
'apollo-people-match': 'person-matched current-employer emails (recovers job movers)',
|
|
93
91
|
},
|
|
94
92
|
verify_email: {
|
|
95
93
|
findymail: 'reliable deliverability verification',
|
package/tests/bulk-tools.test.ts
CHANGED
|
@@ -8,6 +8,7 @@ vi.mock('../src/client.js', () => ({
|
|
|
8
8
|
import { verifyEmailsBulkHandler } from '../src/tools/verify-emails-bulk.js'
|
|
9
9
|
import { findEmailsBulkHandler } from '../src/tools/find-emails-bulk.js'
|
|
10
10
|
import { getBulkJobHandler } from '../src/tools/get-bulk-job.js'
|
|
11
|
+
import { cancelBulkJobHandler } from '../src/tools/cancel-bulk-job.js'
|
|
11
12
|
|
|
12
13
|
beforeEach(() => {
|
|
13
14
|
callApiMock.mockReset()
|
|
@@ -45,4 +46,20 @@ describe('bulk MCP tools', () => {
|
|
|
45
46
|
const res = await verifyEmailsBulkHandler({ emails: ['a@x.com'] })
|
|
46
47
|
expect(res.isError).toBe(true)
|
|
47
48
|
})
|
|
49
|
+
|
|
50
|
+
it('cancel_bulk_job POSTs job_id in the path (empty body) to the cancel endpoint', async () => {
|
|
51
|
+
callApiMock.mockResolvedValue({ ok: true, status: 200, data: { job_id: 42, status: 'cancelled', items_total: 50, items_processed: 12 }, headers: {} })
|
|
52
|
+
const res = await cancelBulkJobHandler({ job_id: 42 })
|
|
53
|
+
const [method, path, body] = callApiMock.mock.calls[0]
|
|
54
|
+
expect(method).toBe('POST')
|
|
55
|
+
expect(path).toBe('/jobs/42/cancel')
|
|
56
|
+
expect(body).toEqual({})
|
|
57
|
+
expect(JSON.parse(res.content[0].text)).toMatchObject({ status: 'cancelled' })
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('cancel_bulk_job surfaces isError on a 400 (not cancellable)', async () => {
|
|
61
|
+
callApiMock.mockResolvedValue({ ok: false, status: 400, data: { error: "can't be cancelled" }, headers: {} })
|
|
62
|
+
const res = await cancelBulkJobHandler({ job_id: 42 })
|
|
63
|
+
expect(res.isError).toBe(true)
|
|
64
|
+
})
|
|
48
65
|
})
|
|
@@ -8,7 +8,6 @@ import { findEmailsBulkSchema } from '../src/tools/find-emails-bulk.js'
|
|
|
8
8
|
import { getEndpointDetailsSchema } from '../src/tools/get-endpoint-details.js'
|
|
9
9
|
import { listDataSourcesHandler } from '../src/tools/list-data-sources.js'
|
|
10
10
|
import {
|
|
11
|
-
CONFIGURED_FIND_EMAILS_PROVIDERS,
|
|
12
11
|
FIND_EMAILS_PROVIDERS,
|
|
13
12
|
getProvidersForCapability,
|
|
14
13
|
resolvePreferredProviders,
|
|
@@ -44,14 +43,15 @@ describe('standalone MCP provider availability', () => {
|
|
|
44
43
|
})
|
|
45
44
|
|
|
46
45
|
it('keeps dormant definitions testable but removes them from every active waterfall', () => {
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// pdl-person-enrich left find_email entirely (2026-07-24 reorder); the dormant
|
|
47
|
+
// PDL definition still lives in find_people for when the account is re-enabled.
|
|
48
|
+
expect(getConfiguredProviders('find_people').some((provider) => provider.id === 'pdl')).toBe(true)
|
|
49
49
|
|
|
50
50
|
for (const capability of CAPABILITIES) {
|
|
51
51
|
expect(getProviders(capability).every((provider) => isProviderEnabled(provider.id))).toBe(true)
|
|
52
52
|
}
|
|
53
53
|
expect(FIND_EMAILS_PROVIDERS.every(isProviderEnabled)).toBe(true)
|
|
54
|
-
expect(getProvidersForCapability('
|
|
54
|
+
expect(getProvidersForCapability('find_people')).not.toContain('pdl')
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
it('rejects an explicit disabled-provider pin without advertising it as available', () => {
|
|
@@ -30,11 +30,11 @@ describe('MCP polling interval schedules', () => {
|
|
|
30
30
|
expect(evalPollSchedule(fn, 300_000).length).toBeGreaterThanOrEqual(10)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
it('
|
|
33
|
+
it('Wiza find-email: 2.5s interval fits inside 60s timeout', () => {
|
|
34
34
|
const providers = getProviders('find_email')
|
|
35
|
-
const
|
|
36
|
-
expect(
|
|
37
|
-
expect(
|
|
35
|
+
const wiza = providers.find((p) => p.id === 'wiza')!
|
|
36
|
+
expect(wiza?.async).toBeDefined()
|
|
37
|
+
expect(wiza.async!.pollIntervalMs).toBe(2500)
|
|
38
38
|
// timeoutMs = 60_000 → at least 24 polls
|
|
39
39
|
expect(60_000 / 2500).toBeGreaterThanOrEqual(24)
|
|
40
40
|
})
|
package/tests/registry.test.ts
CHANGED
|
@@ -651,59 +651,51 @@ describe('registry', () => {
|
|
|
651
651
|
expect(prospeo.hasResult({ email: null })).toBe(false)
|
|
652
652
|
})
|
|
653
653
|
|
|
654
|
-
it('
|
|
654
|
+
it('Wiza maps a linkedin_url to a partial (email-only) individual reveal', () => {
|
|
655
655
|
const providers = getProviders('find_email')
|
|
656
|
-
const
|
|
657
|
-
const result =
|
|
658
|
-
first_name: 'Michel',
|
|
659
|
-
last_name: 'Lieben',
|
|
660
|
-
domain: 'coldiq.com',
|
|
661
|
-
linkedin_url: 'https://linkedin.com/in/michel-lieben',
|
|
662
|
-
})
|
|
656
|
+
const wiza = providers.find((p) => p.id === 'wiza')!
|
|
657
|
+
const result = wiza.mapParams({ linkedin_url: 'https://linkedin.com/in/michel-lieben' })
|
|
663
658
|
expect(result.body).toEqual({
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
last_name: 'Lieben',
|
|
668
|
-
domain: 'coldiq.com',
|
|
669
|
-
linkedin_url: 'https://linkedin.com/in/michel-lieben',
|
|
670
|
-
enrich_fields: ['contact.emails'],
|
|
671
|
-
}],
|
|
659
|
+
individual_reveal: { profile_url: 'https://linkedin.com/in/michel-lieben' },
|
|
660
|
+
enrichment_level: 'partial',
|
|
661
|
+
email_options: { accept_work: true, accept_personal: false },
|
|
672
662
|
})
|
|
673
663
|
})
|
|
674
664
|
|
|
675
|
-
it('
|
|
665
|
+
it('Wiza maps name+domain when no linkedin_url is supplied', () => {
|
|
676
666
|
const providers = getProviders('find_email')
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
expect(
|
|
680
|
-
|
|
681
|
-
|
|
667
|
+
const wiza = providers.find((p) => p.id === 'wiza')!
|
|
668
|
+
const result = wiza.mapParams({ first_name: 'Michel', last_name: 'Lieben', domain: 'coldiq.com' })
|
|
669
|
+
expect((result.body as Record<string, unknown>).individual_reveal).toEqual({
|
|
670
|
+
full_name: 'Michel Lieben', domain: 'coldiq.com',
|
|
671
|
+
})
|
|
682
672
|
})
|
|
683
673
|
|
|
684
|
-
it('
|
|
674
|
+
it('Wiza hasResult reads the reveal envelope; async polls the reveal id', () => {
|
|
685
675
|
const providers = getProviders('find_email')
|
|
686
|
-
const
|
|
687
|
-
expect(
|
|
688
|
-
expect(
|
|
689
|
-
expect(
|
|
690
|
-
expect(
|
|
691
|
-
expect(
|
|
692
|
-
expect(
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
676
|
+
const wiza = providers.find((p) => p.id === 'wiza')!
|
|
677
|
+
expect(wiza.hasResult({ data: { is_complete: true, email: 'michel@coldiq.com' } })).toBe(true)
|
|
678
|
+
expect(wiza.hasResult({ data: { is_complete: true, email: null } })).toBe(false)
|
|
679
|
+
expect(wiza.async).toBeDefined()
|
|
680
|
+
expect(wiza.async!.extractId({ data: { id: 12345 } })).toBe('12345')
|
|
681
|
+
expect(wiza.async!.pollEndpoint('12345')).toBe('/wiza/individual-reveals/12345')
|
|
682
|
+
expect(wiza.async!.isComplete({ data: { is_complete: true } })).toBe(true)
|
|
683
|
+
expect(wiza.async!.isComplete({ data: { status: 'failed' } })).toBe(true)
|
|
684
|
+
expect(wiza.async!.isComplete({ data: { is_complete: false } })).toBe(false)
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
it('Wiza is priority 4 (premium slot after the verifying trio)', () => {
|
|
696
688
|
const providers = getProviders('find_email')
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
expect(fe.priority).toBe(8)
|
|
700
|
-
expect(lu.priority).toBe(10)
|
|
689
|
+
const wiza = providers.find((p) => p.id === 'wiza')!
|
|
690
|
+
expect(wiza.priority).toBe(4)
|
|
701
691
|
})
|
|
702
692
|
|
|
703
|
-
it('
|
|
693
|
+
it('mirrors the server order: findymail → leadmagic → prospeo → wiza → apollo → limadata → icypeas', () => {
|
|
704
694
|
const ids = getProviders('find_email').map((p) => p.id)
|
|
705
|
-
expect(ids
|
|
706
|
-
|
|
695
|
+
expect(ids).toEqual([
|
|
696
|
+
'findymail', 'leadmagic', 'prospeo', 'wiza',
|
|
697
|
+
'apollo-people-match', 'limadata-work-email', 'icypeas',
|
|
698
|
+
])
|
|
707
699
|
})
|
|
708
700
|
|
|
709
701
|
it('Lima Data work-email maps full_name + company_domain', () => {
|
|
@@ -727,47 +719,6 @@ describe('registry', () => {
|
|
|
727
719
|
expect(ld.hasResult({})).toBe(false)
|
|
728
720
|
})
|
|
729
721
|
|
|
730
|
-
it('BlitzAPI maps person_linkedin_url from linkedin_url input', () => {
|
|
731
|
-
const providers = getProviders('find_email')
|
|
732
|
-
const blitz = providers.find((p) => p.id === 'blitzapi')!
|
|
733
|
-
const result = blitz.mapParams({
|
|
734
|
-
linkedin_url: 'https://linkedin.com/in/michel-lieben',
|
|
735
|
-
})
|
|
736
|
-
expect(result.body).toEqual({ person_linkedin_url: 'https://linkedin.com/in/michel-lieben' })
|
|
737
|
-
})
|
|
738
|
-
|
|
739
|
-
it('BlitzAPI isApplicable requires linkedin_url', () => {
|
|
740
|
-
const providers = getProviders('find_email')
|
|
741
|
-
const blitz = providers.find((p) => p.id === 'blitzapi')!
|
|
742
|
-
expect(blitz.isApplicable!({})).toBe(false)
|
|
743
|
-
expect(blitz.isApplicable!({ first_name: 'Michel', domain: 'coldiq.com' })).toBe(false)
|
|
744
|
-
expect(blitz.isApplicable!({ linkedin_url: 'https://...' })).toBe(true)
|
|
745
|
-
})
|
|
746
|
-
|
|
747
|
-
it('BlitzAPI hasResult checks top-level email', () => {
|
|
748
|
-
const providers = getProviders('find_email')
|
|
749
|
-
const blitz = providers.find((p) => p.id === 'blitzapi')!
|
|
750
|
-
expect(blitz.hasResult({ email: 'michel@coldiq.com' })).toBe(true)
|
|
751
|
-
expect(blitz.hasResult({ email: null })).toBe(false)
|
|
752
|
-
})
|
|
753
|
-
|
|
754
|
-
it('Lima Data work-email-linkedin maps linkedin_url passthrough', () => {
|
|
755
|
-
const providers = getProviders('find_email')
|
|
756
|
-
const ld = providers.find((p) => p.id === 'limadata-work-email-linkedin')!
|
|
757
|
-
const result = ld.mapParams({
|
|
758
|
-
linkedin_url: 'https://linkedin.com/in/michel-lieben',
|
|
759
|
-
})
|
|
760
|
-
expect(result.body).toEqual({ linkedin_url: 'https://linkedin.com/in/michel-lieben' })
|
|
761
|
-
})
|
|
762
|
-
|
|
763
|
-
it('Lima Data work-email-linkedin isApplicable requires linkedin_url', () => {
|
|
764
|
-
const providers = getProviders('find_email')
|
|
765
|
-
const ld = providers.find((p) => p.id === 'limadata-work-email-linkedin')!
|
|
766
|
-
expect(ld.isApplicable!({})).toBe(false)
|
|
767
|
-
expect(ld.isApplicable!({ first_name: 'Michel', domain: 'coldiq.com' })).toBe(false)
|
|
768
|
-
expect(ld.isApplicable!({ linkedin_url: 'https://...' })).toBe(true)
|
|
769
|
-
})
|
|
770
|
-
|
|
771
722
|
it('Lima Data work-email isApplicable requires first_name + last_name + domain', () => {
|
|
772
723
|
const providers = getProviders('find_email')
|
|
773
724
|
const ld = providers.find((p) => p.id === 'limadata-work-email')!
|
|
@@ -25,7 +25,7 @@ describe('list_data_sources handler', () => {
|
|
|
25
25
|
expect(names).toContain('TheirStack')
|
|
26
26
|
|
|
27
27
|
const emails = rowFor(caps, 'find_email')!
|
|
28
|
-
expect(emails.sources.map((s) => s.name)).toContain('
|
|
28
|
+
expect(emails.sources.map((s) => s.name)).toContain('Wiza')
|
|
29
29
|
})
|
|
30
30
|
|
|
31
31
|
it('never emits a raw slug or "Apify" in any source name', async () => {
|
|
@@ -79,11 +79,11 @@ describe('resolvePreferredProviders', () => {
|
|
|
79
79
|
|
|
80
80
|
describe('Scenario C — gated provider', () => {
|
|
81
81
|
it('requires gate: message says "requires X, which wasn\'t provided"', () => {
|
|
82
|
-
//
|
|
83
|
-
const r = resolvePreferredProviders('find_email', { first_name: 'Michel', domain: 'coldiq.com' }, ['
|
|
82
|
+
// wiza for find_email requires linkedin_url or full name + company signal
|
|
83
|
+
const r = resolvePreferredProviders('find_email', { first_name: 'Michel', domain: 'coldiq.com' }, ['wiza'])
|
|
84
84
|
expect(r.ok).toBe(false)
|
|
85
85
|
if (!r.ok) {
|
|
86
|
-
expect(r.error.error).toContain("'
|
|
86
|
+
expect(r.error.error).toContain("'wiza' requires linkedin_url or (first_name, last_name, and domain/company_name), which wasn't provided")
|
|
87
87
|
expect(r.error.error).toContain('ColdIQ will automatically pick the best tool')
|
|
88
88
|
}
|
|
89
89
|
})
|
|
@@ -104,14 +104,14 @@ describe('resolvePreferredProviders', () => {
|
|
|
104
104
|
})
|
|
105
105
|
|
|
106
106
|
it('succeeds when gated provider inputs are satisfied', () => {
|
|
107
|
-
//
|
|
107
|
+
// wiza accepts a linkedin_url alone
|
|
108
108
|
const r = resolvePreferredProviders(
|
|
109
109
|
'find_email',
|
|
110
110
|
{ linkedin_url: 'https://www.linkedin.com/in/michel-lieben' },
|
|
111
|
-
['
|
|
111
|
+
['wiza'],
|
|
112
112
|
)
|
|
113
113
|
expect(r.ok).toBe(true)
|
|
114
|
-
if (r.ok) expect(r.providers).toEqual(['
|
|
114
|
+
if (r.ok) expect(r.providers).toEqual(['wiza'])
|
|
115
115
|
})
|
|
116
116
|
})
|
|
117
117
|
|
|
@@ -138,7 +138,7 @@ describe('resolvePreferredProviders', () => {
|
|
|
138
138
|
})
|
|
139
139
|
|
|
140
140
|
it('errors for provider not in find_emails', () => {
|
|
141
|
-
const r = resolvePreferredProviders('find_emails', {}, ['
|
|
141
|
+
const r = resolvePreferredProviders('find_emails', {}, ['fullenrich'])
|
|
142
142
|
expect(r.ok).toBe(false)
|
|
143
143
|
})
|
|
144
144
|
})
|
|
@@ -35,11 +35,11 @@ describe('buildSelectionInsight', () => {
|
|
|
35
35
|
it('states coverage without claiming it was the upfront best fit', () => {
|
|
36
36
|
const r = buildSelectionInsight(
|
|
37
37
|
'find_email',
|
|
38
|
-
'
|
|
38
|
+
'limadata-work-email',
|
|
39
39
|
{ domain: 'coldiq.com', first_name: 'Michel' },
|
|
40
40
|
{ wasFallback: true, pinnedByUser: false },
|
|
41
41
|
)
|
|
42
|
-
expect(r!.insight).toContain('
|
|
42
|
+
expect(r!.insight).toContain('Lima Data')
|
|
43
43
|
expect(r!.insight).toContain('returned the match here')
|
|
44
44
|
// never the confident "Routed to" framing on a fallthrough
|
|
45
45
|
expect(r!.insight.startsWith('Routed to')).toBe(false)
|