Skip to content

Commit 993335a

Browse files
authored
Merge pull request #889 from particle-iot/feature/sc-137525/roll-out-environment-variables-to-devices
Feature/sc 137525/roll out environment variables to devices
2 parents c2d6d8e + 88fba5f commit 993335a

File tree

5 files changed

+510
-9
lines changed

5 files changed

+510
-9
lines changed

src/cli/env-vars.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,41 @@ module.exports = ({ commandProcessor, root }) => {
125125
}
126126
});
127127

128+
commandProcessor.createCommand(envVars, 'rollout', `rollout environment variables ${os.EOL}${aliasDescription} rollout[options]`, {
129+
options: {
130+
'org': {
131+
description: 'Specify the organization'
132+
},
133+
'sandbox': {
134+
description: 'Rollout environment variables to the user\'s sandbox',
135+
boolean: true
136+
},
137+
'product': {
138+
description: 'Specify the product id'
139+
},
140+
'device': {
141+
description: 'Specify the device id'
142+
},
143+
'yes': {
144+
description: 'Skip confirmation and perform the rollout non-interactively',
145+
boolean: true
146+
},
147+
'when': {
148+
description: 'Specify when to rollout the environment variables',
149+
choices: ['immediate', 'connect']
150+
}
151+
},
152+
handler: (args) => {
153+
const EnvVarsCommand = require('../cmd/env-vars');
154+
return new EnvVarsCommand(args).rollout(args);
155+
},
156+
examples: {
157+
'$0 $command --sandbox': 'Rollout environment variables to the user\'s sandbox',
158+
'$0 $command --sandbox --yes': 'Rollout environment variables to user\'s sandbox without confirmation',
159+
'$0 $command --org <org> --yes': 'Rollout environment variables for an organization non-interactively',
160+
'$0 $command --product <productId> --yes': 'Rollout environment variables for a product non-interactively',
161+
'$0 $command --device <deviceId> --yes': 'Rollout environment variables for a device non-interactively',
162+
}
163+
});
164+
128165
};

src/cmd/api.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,25 @@ module.exports = class ParticleApi {
480480
}));
481481
}
482482

483+
performEnvRollout({ org, productId, deviceId, when = 'connect' }) {
484+
const uri = getRolloutUri({ org, productId, deviceId });
485+
return this._wrap(this.api.request({
486+
uri,
487+
method: 'post',
488+
auth: this.accessToken,
489+
data: { when }
490+
}));
491+
}
492+
493+
getRollout({ org, productId, deviceId }) {
494+
const uri = getRolloutUri({ org, productId, deviceId });
495+
return this._wrap(this.api.request({
496+
uri,
497+
method: 'get',
498+
auth: this.accessToken
499+
}));
500+
}
501+
483502
_wrap(promise){
484503
return Promise.resolve(promise)
485504
.then(result => result.body || result)
@@ -564,6 +583,20 @@ function getEnvVarsUri({ org, productId, deviceId }) {
564583
return uri;
565584
}
566585

586+
function getRolloutUri({ org, productId, deviceId }) {
587+
let uri;
588+
if (org) {
589+
uri = `/v1/orgs/${org}/env-vars/rollout`;
590+
} else if (productId) {
591+
uri = `/v1/products/${productId}/env-vars/rollout`;
592+
} else if (deviceId) {
593+
uri = `/v1/devices/${deviceId}/env-vars/rollout`;
594+
} else {
595+
uri = '/v1/env-vars/rollout';
596+
}
597+
return uri;
598+
}
599+
567600
module.exports.UnauthorizedError = class UnauthorizedError extends Error {
568601
constructor(message){
569602
super();

src/cmd/api.test.js

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
'use strict';
2+
const { expect } = require('../../test/setup');
3+
const sinon = require('sinon');
4+
const ParticleApi = require('./api');
5+
6+
describe('ParticleApi', () => {
7+
let particleApi;
8+
let sandbox;
9+
10+
beforeEach(() => {
11+
sandbox = sinon.createSandbox();
12+
particleApi = new ParticleApi('test-base-url', { accessToken: 'test-token' });
13+
});
14+
15+
afterEach(() => {
16+
sandbox.restore();
17+
});
18+
19+
describe('getRollout', () => {
20+
it('should call the correct API endpoint for product rollout', async () => {
21+
const productId = 'testProductId';
22+
const expectedUri = `/v1/products/${productId}/env-vars/rollout`;
23+
const expectedResponse = { body: { some: 'data' } };
24+
25+
const requestStub = sandbox.stub(particleApi.api, 'request').resolves(expectedResponse);
26+
27+
const result = await particleApi.getRollout({ productId });
28+
29+
expect(requestStub).to.have.been.calledWithMatch({
30+
uri: expectedUri,
31+
method: 'get',
32+
auth: 'test-token'
33+
});
34+
expect(result).to.deep.equal(expectedResponse.body);
35+
});
36+
37+
it('should call the correct API endpoint for org rollout', async () => {
38+
const org = 'testOrg';
39+
const expectedUri = `/v1/orgs/${org}/env-vars/rollout`;
40+
const expectedResponse = { body: { some: 'other-data' } };
41+
42+
const requestStub = sandbox.stub(particleApi.api, 'request').resolves(expectedResponse);
43+
44+
const result = await particleApi.getRollout({ org });
45+
46+
expect(requestStub).to.have.been.calledWithMatch({
47+
uri: expectedUri,
48+
method: 'get',
49+
auth: 'test-token'
50+
});
51+
expect(result).to.deep.equal(expectedResponse.body);
52+
});
53+
54+
it('should call the correct API endpoint for sandbox rollout when no org or product is provided', async () => {
55+
const expectedUri = `/v1/env-vars/rollout`;
56+
const expectedResponse = { body: { some: 'sandbox-data' } };
57+
58+
const requestStub = sandbox.stub(particleApi.api, 'request').resolves(expectedResponse);
59+
60+
const result = await particleApi.getRollout({});
61+
62+
expect(requestStub).to.have.been.calledWithMatch({
63+
uri: expectedUri,
64+
method: 'get',
65+
auth: 'test-token'
66+
});
67+
expect(result).to.deep.equal(expectedResponse.body);
68+
});
69+
70+
it('should call the correct API endpoint for device rollout', async () => {
71+
const deviceId = 'testDeviceId';
72+
const expectedUri = `/v1/devices/${deviceId}/env-vars/rollout`;
73+
const expectedResponse = { body: { some: 'sandbox-device-data' } };
74+
75+
const requestStub = sandbox.stub(particleApi.api, 'request').resolves(expectedResponse);
76+
77+
const result = await particleApi.getRollout({ deviceId });
78+
79+
expect(requestStub).to.have.been.calledWithMatch({
80+
uri: expectedUri,
81+
method: 'get',
82+
auth: 'test-token'
83+
});
84+
expect(result).to.deep.equal(expectedResponse.body);
85+
});
86+
87+
it('should handle API errors', async () => {
88+
const productId = 'testProductId';
89+
const expectedError = new Error('API Error');
90+
expectedError.statusCode = 401;
91+
expectedError.body = { error_description: 'Unauthorized' };
92+
93+
sandbox.stub(particleApi.api, 'request').rejects(expectedError);
94+
95+
try {
96+
await particleApi.getRollout({ productId });
97+
expect.fail('should have thrown an error');
98+
} catch (error) {
99+
expect(error.message).to.equal('Unauthorized');
100+
expect(error.name).to.equal('UnauthorizedError');
101+
}
102+
});
103+
});
104+
105+
describe('performEnvRollout', () => {
106+
it('calls the correct API endpoint for product rollout', async () => {
107+
const productId = 'testProductId';
108+
const expectedUri = `/v1/products/${productId}/env-vars/rollout`;
109+
const expectedResponse = { body: { some: 'data' } };
110+
111+
const requestStub = sandbox.stub(particleApi.api, 'request').resolves(expectedResponse);
112+
113+
const result = await particleApi.performEnvRollout({ productId });
114+
115+
expect(requestStub).to.have.been.calledWithMatch({
116+
uri: expectedUri,
117+
method: 'post',
118+
auth: 'test-token',
119+
data: { when: 'connect' }
120+
});
121+
expect(result).to.deep.equal(expectedResponse.body);
122+
});
123+
124+
it('calls the correct API endpoint for org rollout', async () => {
125+
const org = 'testOrg';
126+
const expectedUri = `/v1/orgs/${org}/env-vars/rollout`;
127+
const expectedResponse = { body: { some: 'other-data' } };
128+
129+
const requestStub = sandbox.stub(particleApi.api, 'request').resolves(expectedResponse);
130+
131+
const result = await particleApi.performEnvRollout({ org });
132+
133+
expect(requestStub).to.have.been.calledWithMatch({
134+
uri: expectedUri,
135+
method: 'post',
136+
auth: 'test-token',
137+
data: { when: 'connect' }
138+
});
139+
expect(result).to.deep.equal(expectedResponse.body);
140+
});
141+
142+
it('calls the correct API endpoint for sandbox rollout when no org or product is provided', async () => {
143+
const expectedUri = `/v1/env-vars/rollout`;
144+
const expectedResponse = { body: { some: 'sandbox-data' } };
145+
146+
const requestStub = sandbox.stub(particleApi.api, 'request').resolves(expectedResponse);
147+
148+
const result = await particleApi.performEnvRollout({});
149+
150+
expect(requestStub).to.have.been.calledWithMatch({
151+
uri: expectedUri,
152+
method: 'post',
153+
auth: 'test-token',
154+
data: { when: 'connect' }
155+
});
156+
expect(result).to.deep.equal(expectedResponse.body);
157+
});
158+
159+
it('calls the correct API endpoint for device rollout', async () => {
160+
const deviceId = 'testDeviceId';
161+
const expectedUri = `/v1/devices/${deviceId}/env-vars/rollout`;
162+
const expectedResponse = { body: { some: 'sandbox-device-data' } };
163+
164+
const requestStub = sandbox.stub(particleApi.api, 'request').resolves(expectedResponse);
165+
166+
const result = await particleApi.performEnvRollout({ deviceId, when: 'immediate' });
167+
168+
expect(requestStub).to.have.been.calledWithMatch({
169+
uri: expectedUri,
170+
method: 'post',
171+
auth: 'test-token',
172+
data: { when: 'immediate' }
173+
});
174+
expect(result).to.deep.equal(expectedResponse.body);
175+
});
176+
177+
it('handles API errors', async () => {
178+
const productId = 'testProductId';
179+
const expectedError = new Error('API Error');
180+
expectedError.statusCode = 401;
181+
expectedError.body = { error_description: 'Unauthorized' };
182+
183+
sandbox.stub(particleApi.api, 'request').rejects(expectedError);
184+
185+
try {
186+
await particleApi.performEnvRollout({ productId });
187+
expect.fail('should have thrown an error');
188+
} catch (error) {
189+
expect(error.message).to.equal('Unauthorized');
190+
expect(error.name).to.equal('UnauthorizedError');
191+
}
192+
});
193+
});
194+
});

0 commit comments

Comments
 (0)