@startsimpli/api 0.5.11 → 0.5.13

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startsimpli/api",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "Type-safe Django REST API client for StartSimpli apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -0,0 +1,88 @@
1
+ /**
2
+ * EnrichmentApi unit tests — Apollo enrichment.
3
+ *
4
+ * Phase 2 of user-facing Apollo enrichment (raise-simpli-iz2).
5
+ */
6
+
7
+ import { describe, it, expect, vi } from 'vitest';
8
+ import { EnrichmentApi } from '../lib/enrichment-api';
9
+ import type { ApiClient } from '../lib/api-client';
10
+ import type { ApolloEnrichmentSummary } from '../types/enrichment';
11
+
12
+ function makeClient(postReturn: unknown = {}): ApiClient {
13
+ const post = vi.fn().mockResolvedValue(postReturn);
14
+ return {
15
+ get: vi.fn(),
16
+ post,
17
+ patch: vi.fn(),
18
+ delete: vi.fn(),
19
+ baseUrl: 'http://localhost:8000',
20
+ fetch: { post } as any,
21
+ setTokenGetter: vi.fn(),
22
+ setUnauthorizedHandler: vi.fn(),
23
+ } as unknown as ApiClient;
24
+ }
25
+
26
+ const mockSummary: ApolloEnrichmentSummary = {
27
+ total: 2,
28
+ enriched: ['c-1'],
29
+ skipped: [{ contact_id: 'c-2', reason: 'insufficient query fields' }],
30
+ errors: [],
31
+ missing: [],
32
+ };
33
+
34
+
35
+ describe('EnrichmentApi.enrichApollo', () => {
36
+ it('POSTs to /contacts/enrich-apollo/ with contact_ids body', async () => {
37
+ const client = makeClient(mockSummary);
38
+ const api = new EnrichmentApi(client);
39
+
40
+ const result = await api.enrichApollo(['c-1', 'c-2']);
41
+
42
+ expect(client.fetch.post).toHaveBeenCalledTimes(1);
43
+ const [endpoint, body] = (client.fetch.post as any).mock.calls[0];
44
+ expect(endpoint).toContain('contacts/enrich-apollo');
45
+ expect(body).toEqual({ contact_ids: ['c-1', 'c-2'] });
46
+ expect(result).toEqual(mockSummary);
47
+ });
48
+
49
+ it('returns the structured summary unchanged', async () => {
50
+ const summary: ApolloEnrichmentSummary = {
51
+ total: 3,
52
+ enriched: ['c-1', 'c-3'],
53
+ skipped: [],
54
+ errors: [{ contact_id: 'c-2', error: 'Apollo error: 401' }],
55
+ missing: [],
56
+ };
57
+ const client = makeClient(summary);
58
+ const api = new EnrichmentApi(client);
59
+
60
+ const result = await api.enrichApollo(['c-1', 'c-2', 'c-3']);
61
+ expect(result).toEqual(summary);
62
+ });
63
+
64
+ it('rejects empty contact_ids list before calling the API', async () => {
65
+ const client = makeClient(mockSummary);
66
+ const api = new EnrichmentApi(client);
67
+
68
+ await expect(api.enrichApollo([])).rejects.toThrow(/non-empty/);
69
+ expect(client.fetch.post).not.toHaveBeenCalled();
70
+ });
71
+
72
+ it('rejects > 100 contact_ids before calling the API', async () => {
73
+ const client = makeClient(mockSummary);
74
+ const api = new EnrichmentApi(client);
75
+
76
+ const ids = Array.from({ length: 101 }, (_, i) => `c-${i}`);
77
+ await expect(api.enrichApollo(ids)).rejects.toThrow(/100/);
78
+ expect(client.fetch.post).not.toHaveBeenCalled();
79
+ });
80
+
81
+ it('passes through API errors', async () => {
82
+ const client = makeClient(mockSummary);
83
+ (client.fetch.post as any).mockRejectedValueOnce(new Error('network'));
84
+ const api = new EnrichmentApi(client);
85
+
86
+ await expect(api.enrichApollo(['c-1'])).rejects.toThrow(/network/);
87
+ });
88
+ });
@@ -57,6 +57,7 @@ export const ENDPOINTS = {
57
57
  // Enrichment
58
58
  ENRICHMENT_QUEUE: 'api/v1/enrichment/start-processing',
59
59
  CONTACT_ENRICH: (id: string) => `api/v1/contacts/${id}/enrich`,
60
+ CONTACTS_ENRICH_APOLLO: 'api/v1/contacts/enrich-apollo',
60
61
 
61
62
  // Users
62
63
  USER_ME: 'api/v1/users/me',
@@ -2,13 +2,35 @@
2
2
  * Enrichment API wrapper for /api/v1/enrichment/ and /api/v1/contacts/:id/enrich/
3
3
  */
4
4
 
5
- import type { EnrichmentResult, QueueStatus } from '../types';
5
+ import type { EnrichmentResult, QueueStatus, ApolloEnrichmentSummary } from '../types';
6
6
  import { ENDPOINTS } from '../constants/endpoints';
7
7
  import type { ApiClient } from './api-client';
8
8
 
9
+ const APOLLO_BATCH_CAP = 100;
10
+
9
11
  export class EnrichmentApi {
10
12
  constructor(private client: ApiClient) {}
11
13
 
14
+ /**
15
+ * Trigger Apollo enrichment for one or more contacts.
16
+ *
17
+ * Backend: POST /api/v1/contacts/enrich-apollo/. Team-scoped — contact
18
+ * ids the user can't see come back under `missing`. Calls fail fast on
19
+ * empty list or > 100 ids without round-tripping the server.
20
+ */
21
+ async enrichApollo(contactIds: string[]): Promise<ApolloEnrichmentSummary> {
22
+ if (!Array.isArray(contactIds) || contactIds.length === 0) {
23
+ throw new Error('enrichApollo requires a non-empty list of contact_ids');
24
+ }
25
+ if (contactIds.length > APOLLO_BATCH_CAP) {
26
+ throw new Error(`enrichApollo accepts at most ${APOLLO_BATCH_CAP} contact_ids per call`);
27
+ }
28
+ return this.client.fetch.post<ApolloEnrichmentSummary>(
29
+ ENDPOINTS.CONTACTS_ENRICH_APOLLO,
30
+ { contact_ids: contactIds },
31
+ );
32
+ }
33
+
12
34
  /**
13
35
  * Enrich a single contact by ID
14
36
  */
@@ -26,3 +26,21 @@ export interface IntegrationStatus {
26
26
  description: string;
27
27
  lastSync?: string;
28
28
  }
29
+
30
+ /**
31
+ * Apollo bulk enrichment summary returned by
32
+ * POST /api/v1/contacts/enrich-apollo/.
33
+ *
34
+ * `enriched` — contact ids whose fields were updated by Apollo.
35
+ * `skipped` — contacts the service ran but didn't change (per-entry reason).
36
+ * `errors` — contacts that errored at the Apollo API boundary.
37
+ * `missing` — contact ids the user couldn't see (team-scoped) or that
38
+ * don't exist. Reported back so the UI can flag them.
39
+ */
40
+ export interface ApolloEnrichmentSummary {
41
+ total: number;
42
+ enriched: string[];
43
+ skipped: Array<{ contact_id: string; reason: string }>;
44
+ errors: Array<{ contact_id: string; error: string }>;
45
+ missing: string[];
46
+ }
@@ -95,6 +95,7 @@ export type {
95
95
  QueueStatus,
96
96
  EnrichmentProvider,
97
97
  IntegrationStatus,
98
+ ApolloEnrichmentSummary,
98
99
  } from './enrichment';
99
100
 
100
101
  // Error types