Skip to content

Commit ad225fb

Browse files
Add External ID Field to Contact Model (#128)
* [del-1607] Added tests for inclusion of 'external_id' field in Contact model requests * Responded to PR feedback * Incremented version of this library to 3.11.0
1 parent 9516fac commit ad225fb

6 files changed

Lines changed: 114 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning].
88
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
99
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
1010

11+
## [3.11.0] - 2026-03-16
12+
- Add `external_id` key to Contact model
13+
1114
## [3.10.0] - 2026-01-29
1215
- Add HTTP requests 30 seconds timeout, 120 seconds deadline and retry on timeout
1316

lib/chartmogul/resource.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ const errors = require('./errors');
44
const util = require('./util');
55
const Config = require('./config');
66

7+
// Explicitly strips undefined fields; null is preserved so callers can
8+
// intentionally clear a field (e.g. external_id: null).
9+
function stripUndefined (obj) {
10+
if (!obj || typeof obj !== 'object') return obj;
11+
return JSON.parse(JSON.stringify(obj));
12+
}
13+
714
// HTTP verb mapping
815
const mappings = {
916
all: 'GET',
@@ -53,7 +60,7 @@ class Resource {
5360
}
5461

5562
const qs = method === 'GET' ? data : {};
56-
const body = method === 'GET' ? {} : data;
63+
const body = method === 'GET' ? {} : stripUndefined(data);
5764

5865
const options = {
5966
qs,

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "chartmogul-node",
3-
"version": "3.10.0",
3+
"version": "3.11.0",
44
"description": "Official Chartmogul API Node.js Client",
55
"main": "lib/chartmogul.js",
66
"scripts": {

test/chartmogul/contact.js

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe('Contact', () => {
1313
customer_uuid: 'cus_919d5d7c-9e23-11ed-a936-97fbf69ba02b',
1414
data_source_uuid: 'ds_87832fac-ab61-11ec-a8d8-6fb18044a151',
1515
data_source_customer_external_id: 'scus_606e14228cff7d9db01be55f4e32e5e4',
16+
external_id: 'contact_external_id_001',
1617
first_name: 'First name',
1718
last_name: 'Last name',
1819
position: 9,
@@ -37,6 +38,7 @@ describe('Contact', () => {
3738
customer_uuid: 'cus_00000000-0000-0000-0000-000000000000',
3839
data_source_uuid: 'ds_00000000-0000-0000-0000-000000000000',
3940
data_source_customer_external_id: 'external_001',
41+
external_id: 'contact_external_id_001',
4042
first_name: 'First name',
4143
last_name: 'Last name',
4244
position: 9,
@@ -57,6 +59,70 @@ describe('Contact', () => {
5759
expect(contact).to.have.property('uuid');
5860
});
5961

62+
it('creates a new contact with external_id as null', async () => {
63+
const postBody = {
64+
/* eslint-disable camelcase */
65+
customer_uuid: 'cus_919d5d7c-9e23-11ed-a936-97fbf69ba02b',
66+
data_source_uuid: 'ds_87832fac-ab61-11ec-a8d8-6fb18044a151',
67+
data_source_customer_external_id: 'scus_606e14228cff7d9db01be55f4e32e5e4',
68+
external_id: null,
69+
first_name: 'First name',
70+
last_name: 'Last name',
71+
position: 9,
72+
title: 'Title',
73+
email: 'test@example.com',
74+
phone: '+1234567890',
75+
linked_in: 'https://linkedin.com/not_found',
76+
twitter: 'https://twitter.com/not_found',
77+
notes: 'Heading\nBody\nFooter',
78+
custom: [
79+
{ key: 'Booleanz', value: false },
80+
{ key: 'MyIntegerAttribute', value: 123 }
81+
]
82+
/* eslint-enable camelcase */
83+
};
84+
85+
let requestBody;
86+
nock(config.API_BASE)
87+
.post('/v1/contacts', body => { requestBody = body; return true; })
88+
.reply(200, { uuid: 'con_00000000-0000-0000-0000-000000000000' });
89+
90+
await Contact.create(config, postBody);
91+
// eslint-disable-next-line no-unused-expressions
92+
expect(requestBody).to.have.property('external_id').that.is.null;
93+
});
94+
95+
it('creates a new contact without external_id', async () => {
96+
const postBody = {
97+
/* eslint-disable camelcase */
98+
customer_uuid: 'cus_919d5d7c-9e23-11ed-a936-97fbf69ba02b',
99+
data_source_uuid: 'ds_87832fac-ab61-11ec-a8d8-6fb18044a151',
100+
data_source_customer_external_id: 'scus_606e14228cff7d9db01be55f4e32e5e4',
101+
first_name: 'First name',
102+
last_name: 'Last name',
103+
position: 9,
104+
title: 'Title',
105+
email: 'test@example.com',
106+
phone: '+1234567890',
107+
linked_in: 'https://linkedin.com/not_found',
108+
twitter: 'https://twitter.com/not_found',
109+
notes: 'Heading\nBody\nFooter',
110+
custom: [
111+
{ key: 'Booleanz', value: false },
112+
{ key: 'MyIntegerAttribute', value: 123 }
113+
]
114+
/* eslint-enable camelcase */
115+
};
116+
117+
let requestBody;
118+
nock(config.API_BASE)
119+
.post('/v1/contacts', body => { requestBody = body; return true; })
120+
.reply(200, { uuid: 'con_00000000-0000-0000-0000-000000000000' });
121+
122+
await Contact.create(config, postBody);
123+
expect(requestBody).to.not.have.property('external_id');
124+
});
125+
60126
it('should list all contacts with pagination', async () => {
61127
const query = {
62128
per_page: 1,
@@ -118,6 +184,7 @@ describe('Contact', () => {
118184
customer_uuid: 'cus_00000000-0000-0000-0000-000000000000',
119185
data_source_uuid: 'ds_00000000-0000-0000-0000-000000000000',
120186
customer_external_id: 'external_001',
187+
external_id: 'contact_external_id_001',
121188
first_name: 'First name',
122189
last_name: 'Last name',
123190
position: 9,
@@ -142,23 +209,42 @@ describe('Contact', () => {
142209
const contactUuid = 'con_00000000-0000-0000-0000-000000000000';
143210

144211
/* eslint-disable camelcase */
145-
const postBody = { email: 'test2@example.com' };
212+
const patchBody = { email: 'test2@example.com', external_id: 'contact_external_id_002' };
146213
/* eslint-enable camelcase */
147214

148215
nock(config.API_BASE)
149-
.patch(`/v1/contacts/${contactUuid}`, postBody)
216+
.patch(`/v1/contacts/${contactUuid}`, patchBody)
150217
.reply(200, {
151218
/* eslint-disable camelcase */
152219
uuid: contactUuid,
153220
customer_uuid: 'cus_00000000-0000-0000-0000-000000000000',
154221
data_source_uuid: 'ds_00000000-0000-0000-0000-000000000000',
155222
customer_external_id: 'external_001',
156-
email: 'test2@example.com'
223+
email: 'test2@example.com',
224+
external_id: 'contact_external_id_002'
157225
/* eslint-enable camelcase */
158226
});
159227

160-
const contact = await Contact.modify(config, contactUuid, postBody);
228+
const contact = await Contact.modify(config, contactUuid, patchBody);
161229
expect(contact.email).to.be.equal('test2@example.com');
230+
expect(contact.external_id).to.be.equal('contact_external_id_002');
231+
});
232+
233+
it('updates a contact with external_id as null', async () => {
234+
const contactUuid = 'con_00000000-0000-0000-0000-000000000000';
235+
236+
/* eslint-disable camelcase */
237+
const patchBody = { external_id: null };
238+
/* eslint-enable camelcase */
239+
240+
let requestBody;
241+
nock(config.API_BASE)
242+
.patch(`/v1/contacts/${contactUuid}`, body => { requestBody = body; return true; })
243+
.reply(200, { uuid: contactUuid });
244+
245+
await Contact.modify(config, contactUuid, patchBody);
246+
// eslint-disable-next-line no-unused-expressions
247+
expect(requestBody).to.have.property('external_id').that.is.null;
162248
});
163249

164250
it('deletes a contact', async () => {

test/chartmogul/resource.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ describe('Resource', () => {
7676
expect(e).to.be.instanceOf(ChartMogul.ConfigurationError);
7777
});
7878
});
79+
80+
it('should strip undefined values from POST request body', async () => {
81+
let requestBody;
82+
nock(config.API_BASE)
83+
.post('/', body => { requestBody = body; return true; })
84+
.reply(200, {});
85+
86+
await Resource.request(config, 'POST', '/', { email: 'x', external_id: undefined });
87+
expect(requestBody).to.not.have.property('external_id');
88+
});
7989
});
8090

8191
describe('Resource Retry', () => {

0 commit comments

Comments
 (0)