Skip to content

Commit 350a012

Browse files
authored
Merge pull request #639 from twilio/FLEXY-3631
FLEXY-3631 clean up twilio envrionment when archiving a plugin
2 parents 70ff352 + a89a05e commit 350a012

File tree

8 files changed

+374
-18
lines changed

8 files changed

+374
-18
lines changed

packages/plugin-flex/src/__tests__/clients/ServerlessClient.test.ts

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ import ServerlessClient from '../../clients/ServerlessClient';
77
describe('ServerlessClient', () => {
88
const serviceSid = 'ZS00000000000000000000000000000000';
99
const buildSid = 'ZB00000000000000000000000000000000';
10+
const environmentSid = 'ZE00000000000000000000000000000000';
1011
const pluginName = 'plugin-name';
12+
const environment = { sid: environmentSid, uniqueName: pluginName, buildSid };
1113
const mainPath = '/some/path';
1214
const anotherPath = '/another/path';
1315
const pluginPath = `/plugins/${pluginName}/0.0.0/bundle.js`;
1416

1517
const listEnv = jest.fn();
18+
const removeEnv = jest.fn();
1619
const getBuild = jest.fn();
1720
const getService = jest.fn();
1821
const createService = jest.fn();
22+
const fetch = jest.fn();
1923
getService.mockReturnValue({
24+
fetch,
2025
environments: {
2126
list: listEnv,
2227
},
@@ -32,11 +37,14 @@ describe('ServerlessClient', () => {
3237

3338
// @ts-ignore
3439
const twilioClient = { get: getService, create: createService } as ServiceListInstance;
35-
// @ts-ignore
36-
const client = new ServerlessClient(twilioClient, logger as Logger);
40+
41+
let client: ServerlessClient;
3742

3843
beforeEach(() => {
39-
jest.resetAllMocks();
44+
jest.clearAllMocks();
45+
46+
// @ts-ignore
47+
client = new ServerlessClient(twilioClient, logger as Logger);
4048
});
4149

4250
describe('getLegacyAsset', () => {
@@ -97,6 +105,73 @@ describe('ServerlessClient', () => {
97105
});
98106
});
99107

108+
describe('deleteEnvironment', () => {
109+
it('should delete the environment', async () => {
110+
fetch.mockResolvedValue({
111+
environments: () => ({
112+
get: () => ({
113+
remove: removeEnv,
114+
}),
115+
}),
116+
});
117+
118+
removeEnv.mockResolvedValue(true);
119+
120+
const result = await client.deleteEnvironment(serviceSid, environmentSid);
121+
122+
expect(removeEnv).toHaveBeenCalledTimes(1);
123+
expect(result).toEqual(true);
124+
});
125+
126+
it('should return false if the service does not exist', async () => {
127+
fetch.mockResolvedValue(null);
128+
129+
const result = await client.deleteEnvironment(serviceSid, environmentSid);
130+
131+
expect(removeEnv).toHaveBeenCalledTimes(0);
132+
expect(result).toEqual(false);
133+
});
134+
});
135+
136+
describe('getEnvironment', () => {
137+
it('should get the environment', async () => {
138+
fetch.mockResolvedValue({
139+
environments: () => ({
140+
list: listEnv,
141+
}),
142+
});
143+
listEnv.mockResolvedValue([environment]);
144+
145+
const result = await client.getEnvironment(serviceSid, pluginName);
146+
147+
expect(listEnv).toHaveBeenCalledTimes(1);
148+
expect(result).toEqual(environment);
149+
});
150+
151+
it('should return null if the service does not exist', async () => {
152+
fetch.mockResolvedValue(null);
153+
154+
const result = await client.getEnvironment(serviceSid, pluginName);
155+
156+
expect(listEnv).not.toHaveBeenCalled();
157+
expect(result).toEqual(null);
158+
});
159+
160+
it('should return null if the environment does not exist', async () => {
161+
fetch.mockResolvedValue({
162+
environments: () => ({
163+
list: listEnv,
164+
}),
165+
});
166+
listEnv.mockResolvedValue([]);
167+
168+
const result = await client.getEnvironment(serviceSid, pluginName);
169+
170+
expect(listEnv).toHaveBeenCalledTimes(1);
171+
expect(result).toEqual(null);
172+
});
173+
});
174+
100175
describe('getOrCreateDefaultService', () => {
101176
it('should return existing service', async () => {
102177
const service1 = { uniqueName: 'default' };

packages/plugin-flex/src/__tests__/commands/flex/plugins/archive/plugin-version.test.ts

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import createTest from '../../../../framework';
55
import FlexPluginsArchivePluginVersion from '../../../../../commands/flex/plugins/archive/plugin-version';
66

77
describe('Commands/Archive/FlexPluginsArchivePluginVersion', () => {
8-
const serviceSid = 'ZE00000000000000000000000000000000';
8+
const serviceSid = 'ZS00000000000000000000000000000000';
9+
const environment = { sid: 'ZE00000000000000000000000000000000' };
910
const pluginName = 'plugin-name';
1011
const pluginVersion: PluginVersion = {
1112
sid: 'PV00000000000000000000000000000',
@@ -21,6 +22,8 @@ describe('Commands/Archive/FlexPluginsArchivePluginVersion', () => {
2122
const getServerlessSid = jest.fn();
2223
const getBuild = jest.fn();
2324
const createBuildAndDeploy = jest.fn();
25+
const deleteEnvironment = jest.fn();
26+
const getEnvironment = jest.fn();
2427

2528
const mockPluginsApiToolkit = (cmd: FlexPluginsArchivePluginVersion) => {
2629
// @ts-ignore
@@ -29,8 +32,10 @@ describe('Commands/Archive/FlexPluginsArchivePluginVersion', () => {
2932
// @ts-ignore
3033
jest.spyOn(cmd, 'flexConfigurationClient', 'get').mockReturnValue({ getServerlessSid });
3134

32-
// @ts-ignore
33-
jest.spyOn(cmd, 'serverlessClient', 'get').mockReturnValue({ getBuild, createBuildAndDeploy });
35+
jest
36+
.spyOn(cmd, 'serverlessClient', 'get')
37+
// @ts-ignore
38+
.mockReturnValue({ getBuild, createBuildAndDeploy, getEnvironment, deleteEnvironment });
3439
};
3540
const createCmd = async () =>
3641
createTest(FlexPluginsArchivePluginVersion)('--name', pluginName, '--version', pluginVersion.version);
@@ -171,6 +176,83 @@ describe('Commands/Archive/FlexPluginsArchivePluginVersion', () => {
171176
}
172177
});
173178

179+
it('should clean up environment if there are no more asset and function versions', async () => {
180+
const build = {
181+
serviceSid,
182+
dependencies: {
183+
abc: '123',
184+
},
185+
functionVersions: [],
186+
assetVersions: [
187+
{
188+
sid: 'FV000000000000000000000000000001',
189+
path: `/plugins/${pluginName}/${pluginVersion.version}/bundle.js`,
190+
},
191+
],
192+
};
193+
const cmd = await createCmd();
194+
mockPluginsApiToolkit(cmd);
195+
196+
archivePluginVersion.mockResolvedValue(pluginVersion);
197+
getServerlessSid.mockResolvedValue(serviceSid);
198+
getBuild.mockResolvedValue(build);
199+
getEnvironment.mockResolvedValue(environment);
200+
deleteEnvironment.mockResolvedValue(true);
201+
202+
const result = await cmd.doArchive();
203+
204+
expect(result).toEqual(pluginVersion);
205+
expect(archivePluginVersion).toHaveBeenCalledTimes(1);
206+
expect(archivePluginVersion).toHaveBeenCalledWith({ name: pluginName, version: pluginVersion.version });
207+
expect(describePluginVersion).not.toHaveBeenCalled();
208+
expect(getServerlessSid).toHaveBeenCalledTimes(1);
209+
expect(getBuild).toHaveBeenCalledTimes(1);
210+
expect(getBuild).toHaveBeenCalledWith(serviceSid, pluginName);
211+
expect(getEnvironment).toHaveBeenCalledTimes(1);
212+
expect(deleteEnvironment).toHaveBeenCalledTimes(1);
213+
expect(deleteEnvironment).toHaveBeenCalledWith(serviceSid, environment.sid);
214+
expect(createBuildAndDeploy).not.toHaveBeenCalled();
215+
});
216+
217+
it('should not clean up environment if there are no more asset versions but there are function versions', async () => {
218+
const build = {
219+
serviceSid,
220+
dependencies: {
221+
abc: '123',
222+
},
223+
functionVersions: [
224+
{
225+
sid: 'FV000000000000000000000000000000',
226+
},
227+
],
228+
assetVersions: [
229+
{
230+
sid: 'FV000000000000000000000000000001',
231+
path: `/plugins/${pluginName}/${pluginVersion.version}/bundle.js`,
232+
},
233+
],
234+
};
235+
const cmd = await createCmd();
236+
mockPluginsApiToolkit(cmd);
237+
238+
archivePluginVersion.mockResolvedValue(pluginVersion);
239+
getServerlessSid.mockResolvedValue(serviceSid);
240+
getBuild.mockResolvedValue(build);
241+
242+
const result = await cmd.doArchive();
243+
244+
expect(result).toEqual(pluginVersion);
245+
expect(archivePluginVersion).toHaveBeenCalledTimes(1);
246+
expect(archivePluginVersion).toHaveBeenCalledWith({ name: pluginName, version: pluginVersion.version });
247+
expect(describePluginVersion).not.toHaveBeenCalled();
248+
expect(getServerlessSid).toHaveBeenCalledTimes(1);
249+
expect(getBuild).toHaveBeenCalledTimes(1);
250+
expect(getBuild).toHaveBeenCalledWith(serviceSid, pluginName);
251+
expect(getEnvironment).not.toHaveBeenCalled();
252+
expect(deleteEnvironment).not.toHaveBeenCalled();
253+
expect(createBuildAndDeploy).toHaveBeenCalledTimes(1);
254+
});
255+
174256
it('should archive configuration', async () => {
175257
const cmd = await createCmd();
176258
mockPluginsApiToolkit(cmd);

packages/plugin-flex/src/__tests__/commands/flex/plugins/archive/plugin.test.ts

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { Plugin } from 'flex-plugins-api-toolkit';
2+
import { TwilioApiError, TwilioCliError } from 'flex-dev-utils';
23

34
import createTest from '../../../../framework';
45
import FlexPluginsArchivePlugin from '../../../../../commands/flex/plugins/archive/plugin';
56

67
describe('Commands/Archive/FlexPluginsArchivePlugin', () => {
8+
const serviceSid = 'ZS00000000000000000000000000000000';
9+
const environment = { sid: 'ZE00000000000000000000000000000000' };
710
const plugin: Plugin = {
811
sid: 'FP00000000000000000000000000000',
912
name: 'plugin-name',
@@ -14,10 +17,20 @@ describe('Commands/Archive/FlexPluginsArchivePlugin', () => {
1417
dateUpdated: '',
1518
};
1619
const archivePlugin = jest.fn();
20+
const describePlugin = jest.fn();
21+
const getServerlessSid = jest.fn();
22+
const deleteEnvironment = jest.fn();
23+
const getEnvironment = jest.fn();
1724

1825
const mockPluginsApiToolkit = (cmd: FlexPluginsArchivePlugin) => {
1926
// @ts-ignore
20-
jest.spyOn(cmd, 'pluginsApiToolkit', 'get').mockReturnValue({ archivePlugin });
27+
jest.spyOn(cmd, 'flexConfigurationClient', 'get').mockReturnValue({ getServerlessSid });
28+
29+
// @ts-ignore
30+
jest.spyOn(cmd, 'pluginsApiToolkit', 'get').mockReturnValue({ archivePlugin, describePlugin });
31+
32+
// @ts-ignore
33+
jest.spyOn(cmd, 'serverlessClient', 'get').mockReturnValue({ deleteEnvironment, getEnvironment });
2134
};
2235
const createCmd = async () => createTest(FlexPluginsArchivePlugin)('--name', plugin.name);
2336

@@ -30,11 +43,112 @@ describe('Commands/Archive/FlexPluginsArchivePlugin', () => {
3043
archivePlugin.mockResolvedValue(plugin);
3144
mockPluginsApiToolkit(cmd);
3245

46+
getServerlessSid.mockResolvedValue(serviceSid);
47+
getEnvironment.mockResolvedValue(environment);
48+
deleteEnvironment.mockResolvedValue(true);
49+
3350
const result = await cmd.doArchive();
3451

3552
expect(result).toEqual(plugin);
3653
expect(cmd.pluginsApiToolkit.archivePlugin).toHaveBeenCalledTimes(1);
3754
expect(cmd.pluginsApiToolkit.archivePlugin).toHaveBeenCalledWith({ name: plugin.name });
55+
expect(cmd.serverlessClient.deleteEnvironment).toHaveBeenCalledTimes(1);
56+
expect(cmd.serverlessClient.deleteEnvironment).toHaveBeenCalledWith(serviceSid, environment.sid);
57+
});
58+
59+
it('should quit if already archived and no serviceSid is found', async (done) => {
60+
const err = new TwilioApiError(20400, 'message', 400);
61+
62+
const cmd = await createCmd();
63+
mockPluginsApiToolkit(cmd);
64+
65+
archivePlugin.mockRejectedValue(err);
66+
describePlugin.mockResolvedValue(plugin);
67+
getServerlessSid.mockResolvedValue(null);
68+
69+
try {
70+
await cmd.doArchive();
71+
} catch (e) {
72+
expect(e).toBeInstanceOf(TwilioApiError);
73+
expect(archivePlugin).toHaveBeenCalledTimes(1);
74+
expect(archivePlugin).toHaveBeenCalledWith({ name: plugin.name });
75+
expect(archivePlugin).toHaveBeenCalledTimes(1);
76+
expect(archivePlugin).toHaveBeenCalledWith({ name: plugin.name });
77+
expect(getServerlessSid).toHaveBeenCalledTimes(1);
78+
done();
79+
}
80+
});
81+
82+
it('should quit if already archived and no environment is found', async (done) => {
83+
const err = new TwilioApiError(20400, 'message', 400);
84+
85+
const cmd = await createCmd();
86+
mockPluginsApiToolkit(cmd);
87+
88+
archivePlugin.mockRejectedValue(err);
89+
describePlugin.mockResolvedValue(plugin);
90+
getServerlessSid.mockResolvedValue(serviceSid);
91+
getEnvironment.mockResolvedValue(null);
92+
93+
try {
94+
await cmd.doArchive();
95+
} catch (e) {
96+
expect(e).toBeInstanceOf(TwilioApiError);
97+
expect(archivePlugin).toHaveBeenCalledTimes(1);
98+
expect(archivePlugin).toHaveBeenCalledWith({ name: plugin.name });
99+
expect(archivePlugin).toHaveBeenCalledTimes(1);
100+
expect(archivePlugin).toHaveBeenCalledWith({ name: plugin.name });
101+
expect(getServerlessSid).toHaveBeenCalledTimes(1);
102+
expect(getEnvironment).toHaveBeenCalledTimes(1);
103+
done();
104+
}
105+
});
106+
107+
it('should quit if archive on plugins API is successful but environment cleanup is not', async (done) => {
108+
const cmd = await createCmd();
109+
mockPluginsApiToolkit(cmd);
110+
111+
archivePlugin.mockResolvedValue(plugin);
112+
describePlugin.mockResolvedValue(plugin);
113+
getServerlessSid.mockResolvedValue(serviceSid);
114+
getEnvironment.mockResolvedValue(environment);
115+
deleteEnvironment.mockResolvedValue(false);
116+
117+
try {
118+
await cmd.doArchive();
119+
} catch (e) {
120+
expect(e).toBeInstanceOf(TwilioCliError);
121+
expect(archivePlugin).toHaveBeenCalledTimes(1);
122+
expect(archivePlugin).toHaveBeenCalledWith({ name: plugin.name });
123+
expect(getServerlessSid).toHaveBeenCalledTimes(1);
124+
expect(getEnvironment).toHaveBeenCalledTimes(1);
125+
expect(deleteEnvironment).toHaveBeenCalledTimes(1);
126+
expect(deleteEnvironment).toHaveBeenCalledWith(serviceSid, environment.sid);
127+
done();
128+
}
129+
});
130+
131+
it('should clean up environment if it still exists and plugin is already archived', async () => {
132+
const err = new TwilioApiError(20400, 'message', 400);
133+
134+
const cmd = await createCmd();
135+
mockPluginsApiToolkit(cmd);
136+
137+
archivePlugin.mockRejectedValue(err);
138+
describePlugin.mockResolvedValue(plugin);
139+
getServerlessSid.mockResolvedValue(serviceSid);
140+
getEnvironment.mockResolvedValue(environment);
141+
deleteEnvironment.mockResolvedValue(true);
142+
143+
const result = await cmd.doArchive();
144+
145+
expect(result).toEqual(plugin);
146+
expect(archivePlugin).toHaveBeenCalledTimes(1);
147+
expect(archivePlugin).toHaveBeenCalledWith({ name: plugin.name });
148+
expect(getServerlessSid).toHaveBeenCalledTimes(1);
149+
expect(getEnvironment).toHaveBeenCalledTimes(1);
150+
expect(deleteEnvironment).toHaveBeenCalledTimes(1);
151+
expect(deleteEnvironment).toHaveBeenCalledWith(serviceSid, environment.sid);
38152
});
39153

40154
it('should get name', async () => {

packages/plugin-flex/src/__tests__/sub-commands/archive-resource.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ describe('SubCommands/ArchiveResource', () => {
6060

6161
expect(doArchive).toHaveBeenCalledTimes(1);
6262
expect(getName).toHaveBeenCalledTimes(2);
63-
expect(getResourceType).toHaveBeenCalledTimes(1);
63+
expect(getResourceType).toHaveBeenCalledTimes(2);
6464
expect(confirm).toHaveBeenCalledTimes(1);
6565
expect(exit).not.toHaveBeenCalled();
6666

@@ -79,7 +79,7 @@ describe('SubCommands/ArchiveResource', () => {
7979

8080
expect(doArchive).not.toHaveBeenCalled();
8181
expect(getName).toHaveBeenCalledTimes(2);
82-
expect(getResourceType).toHaveBeenCalledTimes(1);
82+
expect(getResourceType).toHaveBeenCalledTimes(2);
8383
expect(confirm).toHaveBeenCalledTimes(1);
8484
expect(exit).toHaveBeenCalledTimes(1);
8585
expect(exit).toHaveBeenCalledWith(0);

0 commit comments

Comments
 (0)