@coldiq/mcp 0.2.4 → 0.2.6

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.
@@ -1,6 +1,66 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
2
  import { initClient } from '../../src/client.js'
3
- import { verifyEmailHandler } from '../../src/tools/verify-email.js'
3
+ import { verifyEmailHandler, addNormalizedStatus } from '../../src/tools/verify-email.js'
4
+
5
+ describe('addNormalizedStatus', () => {
6
+ it('findymail verified=true → status valid', () => {
7
+ const out = addNormalizedStatus({ email: 'michel@coldiq.com', verified: true, provider: 'Google' }, 'findymail') as Record<string, unknown>
8
+ expect(out.status).toBe('valid')
9
+ // Original fields preserved for back-compat.
10
+ expect(out.verified).toBe(true)
11
+ expect(out.provider).toBe('Google')
12
+ })
13
+
14
+ it('findymail verified=false → status invalid', () => {
15
+ const out = addNormalizedStatus({ email: 'bad@example.com', verified: false, provider: 'Other' }, 'findymail') as Record<string, unknown>
16
+ expect(out.status).toBe('invalid')
17
+ })
18
+
19
+ it('icypeas item.status=catch_all → catch_all', () => {
20
+ const out = addNormalizedStatus({ success: true, item: { status: 'catch_all', email: 'x@y.com' } }, 'icypeas') as Record<string, unknown>
21
+ expect(out.status).toBe('catch_all')
22
+ })
23
+
24
+ it('icypeas missing item → unknown', () => {
25
+ const out = addNormalizedStatus({ success: true }, 'icypeas') as Record<string, unknown>
26
+ expect(out.status).toBe('unknown')
27
+ })
28
+
29
+ it('upstream status="catchall" is normalized to catch_all', () => {
30
+ const out = addNormalizedStatus({ status: 'catchall', email: 'x@y.com' }, 'instantly') as Record<string, unknown>
31
+ expect(out.status).toBe('catch_all')
32
+ })
33
+
34
+ it('upstream status="risky" stays risky', () => {
35
+ const out = addNormalizedStatus({ status: 'risky' }, 'instantly') as Record<string, unknown>
36
+ expect(out.status).toBe('risky')
37
+ })
38
+
39
+ it('upstream status="disposable" stays disposable', () => {
40
+ const out = addNormalizedStatus({ status: 'disposable' }, 'instantly') as Record<string, unknown>
41
+ expect(out.status).toBe('disposable')
42
+ })
43
+
44
+ it('upstream status="VALID" is lowercased to valid', () => {
45
+ const out = addNormalizedStatus({ status: 'VALID' }, 'instantly') as Record<string, unknown>
46
+ expect(out.status).toBe('valid')
47
+ })
48
+
49
+ it('unknown provider id with no signal fields → unknown', () => {
50
+ const out = addNormalizedStatus({ foo: 'bar' }, 'mystery-provider') as Record<string, unknown>
51
+ expect(out.status).toBe('unknown')
52
+ })
53
+
54
+ it('null / non-object data is returned unchanged', () => {
55
+ expect(addNormalizedStatus(null, 'findymail')).toBe(null)
56
+ expect(addNormalizedStatus('a string', 'findymail')).toBe('a string')
57
+ })
58
+
59
+ it('linkupapi-validate verified=true → valid', () => {
60
+ const out = addNormalizedStatus({ verified: true }, 'linkupapi-validate') as Record<string, unknown>
61
+ expect(out.status).toBe('valid')
62
+ })
63
+ })
4
64
 
5
65
  describe('verify_email handler', () => {
6
66
  const originalFetch = globalThis.fetch
@@ -13,9 +73,9 @@ describe('verify_email handler', () => {
13
73
  globalThis.fetch = originalFetch
14
74
  })
15
75
 
16
- it('returns verification result from FindyMail', async () => {
76
+ it('normalizes findymail real upstream shape (verified boolean) to status=valid', async () => {
17
77
  globalThis.fetch = vi.fn(async () =>
18
- new Response(JSON.stringify({ status: 'valid', email: 'michel@coldiq.com' }), { status: 200 })
78
+ new Response(JSON.stringify({ email: 'michel@coldiq.com', verified: true, provider: 'Google' }), { status: 200 })
19
79
  ) as typeof fetch
20
80
 
21
81
  const result = await verifyEmailHandler({ email: 'michel@coldiq.com' })
@@ -23,6 +83,20 @@ describe('verify_email handler', () => {
23
83
  const parsed = JSON.parse(result.content[0].text)
24
84
  expect(parsed._meta.provider).toBe('findymail')
25
85
  expect(parsed.data.status).toBe('valid')
86
+ // Raw fields still surfaced for callers that already depend on them.
87
+ expect(parsed.data.verified).toBe(true)
88
+ expect(parsed.data.provider).toBe('Google')
89
+ })
90
+
91
+ it('normalizes findymail verified=false to status=invalid', async () => {
92
+ globalThis.fetch = vi.fn(async () =>
93
+ new Response(JSON.stringify({ email: 'bad@example.com', verified: false, provider: 'Other' }), { status: 200 })
94
+ ) as typeof fetch
95
+
96
+ const result = await verifyEmailHandler({ email: 'bad@example.com' })
97
+
98
+ const parsed = JSON.parse(result.content[0].text)
99
+ expect(parsed.data.status).toBe('invalid')
26
100
  })
27
101
 
28
102
  it('falls back to IcyPeas on FindyMail failure', async () => {
@@ -35,12 +35,15 @@ describe('extractPeopleArray', () => {
35
35
  expect(extractPeopleArray({ people: [{ name: 'A' }] }, 'fullenrich-people-search')).toHaveLength(1)
36
36
  })
37
37
 
38
- it('pdl / prospeo / companyenrich: top-level data[]', () => {
38
+ it('pdl / companyenrich: top-level data[]', () => {
39
39
  expect(extractPeopleArray({ data: [{ full_name: 'A' }] }, 'pdl')).toHaveLength(1)
40
- expect(extractPeopleArray({ data: [{ name: 'B' }] }, 'prospeo-search-person')).toHaveLength(1)
41
40
  expect(extractPeopleArray({ data: [{ name: 'C' }] }, 'companyenrich')).toHaveLength(1)
42
41
  })
43
42
 
43
+ it('prospeo-search-person: top-level results[] (real upstream shape)', () => {
44
+ expect(extractPeopleArray({ results: [{ person: { full_name: 'B' } }] }, 'prospeo-search-person')).toHaveLength(1)
45
+ })
46
+
44
47
  it('ai-ark-people: reads content[]', () => {
45
48
  expect(extractPeopleArray({ content: [{ name: 'A' }] }, 'ai-ark-people')).toHaveLength(1)
46
49
  })
@@ -286,6 +289,28 @@ describe('normalizePerson — ai-ark profile-nested shape', () => {
286
289
  })
287
290
  })
288
291
 
292
+ describe('normalizePerson — prospeo person-nested shape', () => {
293
+ // Prospeo /search-person returns { results: [{ person: { full_name, current_job_title, ... } }] }.
294
+ // Verified against the live api.prospeo.io/search-person response.
295
+ it('unwraps record.person and reads current_job_title as title', () => {
296
+ const p = normalizePerson({
297
+ person: {
298
+ person_id: 'aaaa07ad691a3a7f2ac039c6',
299
+ full_name: 'Catherine Powlett',
300
+ first_name: 'Catherine',
301
+ last_name: 'Powlett',
302
+ current_job_title: 'Marketing Manager',
303
+ linkedin_url: 'https://www.linkedin.com/in/catherinepowlett',
304
+ },
305
+ })!
306
+ expect(p.full_name).toBe('Catherine Powlett')
307
+ expect(p.first_name).toBe('Catherine')
308
+ expect(p.last_name).toBe('Powlett')
309
+ expect(p.title).toBe('Marketing Manager')
310
+ expect(p.linkedin_url).toBe('https://www.linkedin.com/in/catherinepowlett')
311
+ })
312
+ })
313
+
289
314
  describe('normalizePerson — leadsfactory persona-level company sibling', () => {
290
315
  // LF persona items have shape { contact, company, persona_index, persona_job_title }.
291
316
  // Verified against src/providers/leadsfactory/schema.ts:134-141. The company sibling