Skip to content

Commit b87ccd9

Browse files
committed
add integration tests
1 parent c21498e commit b87ccd9

15 files changed

+562
-2
lines changed

.eslintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"globals": {
77
"API_KEY": false,
88
"expect": false,
9+
"HAS_LIVE_KEY": false,
10+
"INTEGRATION_TIMEOUT": false,
11+
"LiveLob": false,
912
"Lob": false,
1013
"mocks": false
1114
}

.github/workflows/run_tests.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Run Tests
22

3-
on: [pull_request]
3+
on: [push, pull_request]
44

55
jobs:
66
node_tests:
@@ -20,6 +20,11 @@ jobs:
2020
run: npm install --legacy-peer-deps
2121
- name: Run tests
2222
run: npm run test
23+
- name: Run integration tests
24+
env:
25+
TEST_API_KEY: ${{ secrets.TEST_API_KEY }}
26+
LIVE_API_KEY: ${{ secrets.LIVE_API_KEY }}
27+
run: npm run test:integration
2328
- name: Coverage
2429
run: npm run test-lcov
2530
- name: Coveralls

.mocharc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"recursive": true,
33
"timeout": 30000,
4-
"require": ["./test/setup.js"]
4+
"require": ["./test/setup.js"],
5+
"ignore": ["test/integration/**"]
56
}

lib/resources/resourceBase.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class ResourceBase {
3939
}
4040

4141
let isMultiPartForm = false;
42+
let requiresJson = false;
4243

4344
for (const key in form) {
4445
if (form[key] === undefined) {
@@ -61,6 +62,11 @@ class ResourceBase {
6162
isMultiPartForm = true;
6263
break;
6364
}
65+
66+
// Check if array contains objects (requires JSON encoding)
67+
if (Array.isArray(val) && val.length > 0 && typeof val[0] === 'object') {
68+
requiresJson = true;
69+
}
6470
}
6571

6672
if (qs) {
@@ -85,6 +91,10 @@ class ResourceBase {
8591

8692
config.data = formData;
8793
config.headers = Object.assign({}, config.headers, formData.getHeaders());
94+
} else if (requiresJson) {
95+
// Use JSON for requests with nested objects (e.g., bulk verifications)
96+
config.data = form;
97+
config.headers['Content-Type'] = 'application/json';
8898
} else {
8999
const params = new URLSearchParams();
90100
for (const key in form) {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
},
4747
"scripts": {
4848
"test": "cross-env NODE_ENV=test nyc mocha test",
49+
"test:integration": "mocha --config test/integration/.mocharc.json",
4950
"test-no-cover": "cross-env NODE_ENV=test mocha test",
5051
"coverage": "nyc report --reporter=text",
5152
"enforce": "nyc check-coverage --statements 100 --lines 100 --functions 100 --branches 100",

test/integration/.mocharc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"recursive": true,
3+
"timeout": 30000,
4+
"require": ["./test/integration/setup.js"],
5+
"spec": "test/integration/**/*.test.js"
6+
}

test/integration/addresses.test.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
'use strict';
2+
3+
describe('Integration: Addresses', function () {
4+
5+
this.timeout(INTEGRATION_TIMEOUT);
6+
7+
let createdAddressId;
8+
9+
const TEST_ADDRESS = {
10+
name: 'Integration Test',
11+
address_line1: '185 Berry St',
12+
address_line2: 'Ste 6100',
13+
address_city: 'San Francisco',
14+
address_state: 'CA',
15+
address_zip: '94107',
16+
address_country: 'US'
17+
};
18+
19+
describe('create', () => {
20+
21+
it('creates an address with valid data', (done) => {
22+
Lob.addresses.create(TEST_ADDRESS, (err, res) => {
23+
expect(err).to.not.exist;
24+
expect(res).to.have.property('id');
25+
expect(res.id).to.match(/^adr_/);
26+
expect(res.name).to.eql('INTEGRATION TEST');
27+
expect(res.address_city).to.eql('SAN FRANCISCO');
28+
expect(res.object).to.eql('address');
29+
createdAddressId = res.id;
30+
done();
31+
});
32+
});
33+
34+
it('rejects an address with invalid data', (done) => {
35+
Lob.addresses.create({
36+
name: 'Bad Address',
37+
address_line1: '123 Fake Street',
38+
address_city: 'Nonexistent City',
39+
address_state: 'XX',
40+
address_zip: '00000',
41+
address_country: 'US'
42+
}, (err) => {
43+
expect(err).to.exist;
44+
expect(err.message).to.exist;
45+
done();
46+
});
47+
});
48+
49+
});
50+
51+
describe('retrieve', () => {
52+
53+
it('retrieves a created address', function (done) {
54+
if (!createdAddressId) {
55+
return this.skip();
56+
}
57+
Lob.addresses.retrieve(createdAddressId, (err, res) => {
58+
expect(err).to.not.exist;
59+
expect(res.id).to.eql(createdAddressId);
60+
expect(res.object).to.eql('address');
61+
done();
62+
});
63+
});
64+
65+
it('returns error for non-existent address', (done) => {
66+
Lob.addresses.retrieve('adr_nonexistent123456', (err) => {
67+
expect(err).to.exist;
68+
expect(err.status_code).to.eql(404);
69+
done();
70+
});
71+
});
72+
73+
});
74+
75+
describe('list', () => {
76+
77+
it('returns a list of addresses', (done) => {
78+
Lob.addresses.list({ limit: 5 }, (err, res) => {
79+
expect(err).to.not.exist;
80+
expect(res.object).to.eql('list');
81+
expect(res.data).to.be.instanceof(Array);
82+
expect(res.data.length).to.be.at.most(5);
83+
done();
84+
});
85+
});
86+
87+
});
88+
89+
describe('delete', () => {
90+
91+
it('deletes a created address', function (done) {
92+
if (!createdAddressId) {
93+
return this.skip();
94+
}
95+
Lob.addresses.delete(createdAddressId, (err, res) => {
96+
expect(err).to.not.exist;
97+
expect(res.id).to.eql(createdAddressId);
98+
expect(res.deleted).to.eql(true);
99+
done();
100+
});
101+
});
102+
103+
});
104+
105+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
'use strict';
2+
3+
describe('Integration: Bulk Intl Verifications (Live Key Required)', function () {
4+
5+
this.timeout(INTEGRATION_TIMEOUT);
6+
7+
before(function () {
8+
if (!HAS_LIVE_KEY) {
9+
this.skip();
10+
}
11+
});
12+
13+
describe('verify', () => {
14+
15+
it('verifies an international address', (done) => {
16+
LiveLob.bulkIntlVerifications.verify({
17+
addresses: [
18+
{
19+
primary_line: '370 Water St',
20+
city: 'Summerside',
21+
state: 'Prince Edward Island',
22+
postal_code: 'C1N 1C4',
23+
country: 'CA'
24+
}
25+
]
26+
}, (err, res) => {
27+
expect(err).to.not.exist;
28+
expect(res).to.have.property('addresses');
29+
expect(res.addresses).to.be.instanceof(Array);
30+
expect(res.addresses.length).to.eql(1);
31+
done();
32+
});
33+
});
34+
35+
});
36+
37+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
describe('Integration: Bulk US Verifications (Live Key Required)', function () {
4+
5+
this.timeout(INTEGRATION_TIMEOUT);
6+
7+
before(function () {
8+
if (!HAS_LIVE_KEY) {
9+
this.skip();
10+
}
11+
});
12+
13+
describe('verify', () => {
14+
15+
it('verifies multiple addresses', (done) => {
16+
LiveLob.bulkUSVerifications.verify({
17+
addresses: [
18+
{
19+
primary_line: '185 Berry St Ste 6100',
20+
city: 'San Francisco',
21+
state: 'CA',
22+
zip_code: '94107'
23+
},
24+
{
25+
primary_line: '210 King St',
26+
city: 'San Francisco',
27+
state: 'CA',
28+
zip_code: '94107'
29+
}
30+
]
31+
}, (err, res) => {
32+
expect(err).to.not.exist;
33+
expect(res).to.have.property('addresses');
34+
expect(res.addresses).to.be.instanceof(Array);
35+
expect(res.addresses.length).to.eql(2);
36+
done();
37+
});
38+
});
39+
40+
});
41+
42+
});

test/integration/campaigns.test.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'use strict';
2+
3+
describe('Integration: Campaigns (Live Key Required)', function () {
4+
5+
this.timeout(INTEGRATION_TIMEOUT);
6+
7+
let createdCampaignId;
8+
9+
before(function () {
10+
if (!HAS_LIVE_KEY) {
11+
this.skip();
12+
}
13+
});
14+
15+
describe('create', () => {
16+
17+
it('creates a campaign', (done) => {
18+
LiveLob.campaigns.create({
19+
name: 'Integration Test Campaign',
20+
schedule_type: 'immediate'
21+
}, (err, res) => {
22+
expect(err).to.not.exist;
23+
expect(res).to.have.property('id');
24+
expect(res.id).to.match(/^cmp_/);
25+
expect(res.name).to.eql('Integration Test Campaign');
26+
createdCampaignId = res.id;
27+
done();
28+
});
29+
});
30+
31+
});
32+
33+
describe('retrieve', () => {
34+
35+
it('retrieves a created campaign', function (done) {
36+
if (!createdCampaignId) {
37+
return this.skip();
38+
}
39+
LiveLob.campaigns.retrieve(createdCampaignId, (err, res) => {
40+
expect(err).to.not.exist;
41+
expect(res.id).to.eql(createdCampaignId);
42+
done();
43+
});
44+
});
45+
46+
});
47+
48+
describe('list', () => {
49+
50+
it('returns a list of campaigns', (done) => {
51+
LiveLob.campaigns.list({ limit: 5 }, (err, res) => {
52+
expect(err).to.not.exist;
53+
expect(res.object).to.eql('list');
54+
expect(res.data).to.be.instanceof(Array);
55+
done();
56+
});
57+
});
58+
59+
});
60+
61+
describe('delete', () => {
62+
63+
it('deletes a created campaign', function (done) {
64+
if (!createdCampaignId) {
65+
return this.skip();
66+
}
67+
LiveLob.campaigns.delete(createdCampaignId, (err, res) => {
68+
expect(err).to.not.exist;
69+
expect(res.id).to.eql(createdCampaignId);
70+
expect(res.deleted).to.eql(true);
71+
done();
72+
});
73+
});
74+
75+
});
76+
77+
});

0 commit comments

Comments
 (0)