@unito/integration-cli 0.66.0 → 1.0.0
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/dist/src/commands/graph.d.ts +3 -0
- package/dist/src/commands/graph.js +91 -18
- package/dist/test/commands/graph.test.js +201 -11
- package/oclif.manifest.json +35 -5
- package/package.json +1 -1
|
@@ -16,6 +16,9 @@ export default class Graph extends BaseCommand<typeof Graph> {
|
|
|
16
16
|
'credential-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
17
|
'config-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
18
|
output: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
|
+
body: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
+
'body-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
19
22
|
};
|
|
20
23
|
run(): Promise<void>;
|
|
21
24
|
}
|
|
@@ -4,6 +4,7 @@ const tslib_1 = require("tslib");
|
|
|
4
4
|
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
|
5
5
|
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
6
6
|
const core_1 = require("@oclif/core");
|
|
7
|
+
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
7
8
|
const baseCommand_1 = require("../baseCommand");
|
|
8
9
|
const errors_1 = require("../errors");
|
|
9
10
|
const GlobalConfiguration = tslib_1.__importStar(require("../resources/globalConfiguration"));
|
|
@@ -11,18 +12,28 @@ const integrations_1 = require("../resources/integrations");
|
|
|
11
12
|
const configuration_1 = require("../resources/configuration");
|
|
12
13
|
const decryption_1 = require("../resources/decryption");
|
|
13
14
|
const credentials_1 = require("../resources/credentials");
|
|
15
|
+
const METHOD_MAP = {
|
|
16
|
+
getItem: 'GET',
|
|
17
|
+
getCollection: 'GET',
|
|
18
|
+
createItem: 'POST',
|
|
19
|
+
updateItem: 'PATCH',
|
|
20
|
+
deleteItem: 'DELETE',
|
|
21
|
+
};
|
|
14
22
|
class Graph extends baseCommand_1.BaseCommand {
|
|
15
23
|
static description = 'Query a running integration graph and print the response';
|
|
16
24
|
static examples = [
|
|
17
|
-
'<%= config.bin %> <%= command.id %> --path=/sobjects/Opportunity/records/abc123',
|
|
18
|
-
'<%= config.bin %> <%= command.id %>
|
|
25
|
+
'<%= config.bin %> <%= command.id %> getItem --path=/sobjects/Opportunity/records/abc123',
|
|
26
|
+
'<%= config.bin %> <%= command.id %> getCollection --path=/sobjects/Opportunity --port=9201',
|
|
27
|
+
`<%= config.bin %> <%= command.id %> createItem --path=/sobjects/Task/records --body='{"Name":"New Task"}'`,
|
|
28
|
+
'<%= config.bin %> <%= command.id %> updateItem --path=/sobjects/Task/records/abc123 --body=\'{"Name":"Updated"}\'',
|
|
29
|
+
'<%= config.bin %> <%= command.id %> deleteItem --path=/sobjects/Task/records/abc123',
|
|
19
30
|
];
|
|
20
31
|
static args = {
|
|
21
32
|
operation: core_1.Args.string({
|
|
22
33
|
description: 'Operation to perform on the graph path',
|
|
23
34
|
required: false,
|
|
24
|
-
default: '
|
|
25
|
-
options: ['
|
|
35
|
+
default: 'getItem',
|
|
36
|
+
options: ['getItem', 'getCollection', 'createItem', 'updateItem', 'deleteItem'],
|
|
26
37
|
}),
|
|
27
38
|
};
|
|
28
39
|
async catch(error) {
|
|
@@ -72,13 +83,32 @@ class Graph extends baseCommand_1.BaseCommand {
|
|
|
72
83
|
char: 'o',
|
|
73
84
|
description: 'Write response body to file instead of stdout',
|
|
74
85
|
}),
|
|
86
|
+
body: core_1.Flags.string({
|
|
87
|
+
char: 'b',
|
|
88
|
+
description: 'JSON request body for createItem/updateItem operations',
|
|
89
|
+
exclusive: ['body-file'],
|
|
90
|
+
}),
|
|
91
|
+
'body-file': core_1.Flags.string({
|
|
92
|
+
description: 'Path to a JSON file to use as request body',
|
|
93
|
+
exclusive: ['body'],
|
|
94
|
+
}),
|
|
95
|
+
yes: core_1.Flags.boolean({
|
|
96
|
+
char: 'y',
|
|
97
|
+
description: 'Skip confirmation prompt for destructive operations (deleteItem)',
|
|
98
|
+
default: false,
|
|
99
|
+
}),
|
|
75
100
|
};
|
|
76
101
|
async run() {
|
|
77
102
|
(0, integrations_1.validateIsIntegrationDirectory)();
|
|
78
103
|
const { args, flags } = await this.parse(Graph);
|
|
79
|
-
const operation = args.operation
|
|
80
|
-
|
|
81
|
-
|
|
104
|
+
const operation = args.operation;
|
|
105
|
+
const method = METHOD_MAP[operation];
|
|
106
|
+
const needsBody = method === 'POST' || method === 'PATCH';
|
|
107
|
+
if (needsBody && !flags.body && !flags['body-file']) {
|
|
108
|
+
this.error('--body or --body-file is required for ' + operation, { exit: 1 });
|
|
109
|
+
}
|
|
110
|
+
if (operation === 'deleteItem' && (flags.body || flags['body-file'])) {
|
|
111
|
+
this.error('--body and --body-file are not supported for deleteItem', { exit: 1 });
|
|
82
112
|
}
|
|
83
113
|
const environment = flags.environment ?? GlobalConfiguration.Environment.Production;
|
|
84
114
|
const configuration = await (0, configuration_1.getConfiguration)(environment, flags['config-path']);
|
|
@@ -122,22 +152,65 @@ class Graph extends baseCommand_1.BaseCommand {
|
|
|
122
152
|
'X-Unito-Correlation-Id': crypto_1.default.randomUUID(),
|
|
123
153
|
'Content-Type': 'application/json',
|
|
124
154
|
};
|
|
155
|
+
let body;
|
|
156
|
+
if (needsBody) {
|
|
157
|
+
if (flags.body) {
|
|
158
|
+
try {
|
|
159
|
+
JSON.parse(flags.body);
|
|
160
|
+
body = flags.body;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
this.error('Invalid JSON in --body: ' + flags.body, { exit: 1 });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (flags['body-file']) {
|
|
167
|
+
const raw = await fs_1.default.promises.readFile(flags['body-file'], 'utf8').catch(() => {
|
|
168
|
+
this.error('Could not read --body-file: ' + flags['body-file'], { exit: 1 });
|
|
169
|
+
});
|
|
170
|
+
try {
|
|
171
|
+
JSON.parse(raw);
|
|
172
|
+
body = raw;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
this.error('Invalid JSON in --body-file: ' + flags['body-file'], { exit: 1 });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
125
179
|
const url = `http://localhost:${flags.port}${flags.path}`;
|
|
126
|
-
|
|
180
|
+
if (operation === 'deleteItem' && !flags.yes) {
|
|
181
|
+
const { proceed } = await inquirer_1.default.prompt({
|
|
182
|
+
name: 'proceed',
|
|
183
|
+
message: `About to DELETE ${flags.path}. Continue?`,
|
|
184
|
+
type: 'confirm',
|
|
185
|
+
default: false,
|
|
186
|
+
});
|
|
187
|
+
if (!proceed) {
|
|
188
|
+
this.log('Aborted.');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const fetchOptions = { method, headers };
|
|
193
|
+
if (body) {
|
|
194
|
+
fetchOptions.body = body;
|
|
195
|
+
}
|
|
196
|
+
const response = await fetch(url, fetchOptions);
|
|
127
197
|
if (!response.ok) {
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
this.logToStderr(JSON.stringify(
|
|
198
|
+
const errorBody = await response.json().catch(() => undefined);
|
|
199
|
+
if (errorBody !== undefined) {
|
|
200
|
+
this.logToStderr(JSON.stringify(errorBody, null, 2));
|
|
131
201
|
}
|
|
132
202
|
this.error(`HTTP ${response.status} from ${url}`, { exit: response.status });
|
|
133
203
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
204
|
+
const hasContent = response.status !== 204;
|
|
205
|
+
if (hasContent) {
|
|
206
|
+
const responseBody = (await response.json());
|
|
207
|
+
const json = JSON.stringify(responseBody, null, 2);
|
|
208
|
+
if (flags.output) {
|
|
209
|
+
await fs_1.default.promises.writeFile(flags.output, json, 'utf8');
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
this.log(json);
|
|
213
|
+
}
|
|
141
214
|
}
|
|
142
215
|
}
|
|
143
216
|
}
|
|
@@ -3,10 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
5
5
|
const test_1 = require("@oclif/test");
|
|
6
|
+
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
6
7
|
const sinon_1 = tslib_1.__importDefault(require("sinon"));
|
|
7
8
|
const Configuration = tslib_1.__importStar(require("../../src/resources/configuration"));
|
|
8
9
|
const Decryption = tslib_1.__importStar(require("../../src/resources/decryption"));
|
|
9
10
|
const Integrations = tslib_1.__importStar(require("../../src/resources/integrations"));
|
|
11
|
+
const originalReadFile = fs_1.default.promises.readFile.bind(fs_1.default.promises);
|
|
10
12
|
const MOCK_CONFIG = {
|
|
11
13
|
name: 'salesforce-v2',
|
|
12
14
|
testAccounts: {
|
|
@@ -141,8 +143,8 @@ describe('graph', () => {
|
|
|
141
143
|
json: () => Promise.resolve(MOCK_RESPONSE),
|
|
142
144
|
}))
|
|
143
145
|
.stdout()
|
|
144
|
-
.command(['graph', '
|
|
145
|
-
.it('accepts explicit "
|
|
146
|
+
.command(['graph', 'getItem', '--path=/sobjects/Opportunity'])
|
|
147
|
+
.it('accepts explicit "getItem" operation arg', ctx => {
|
|
146
148
|
(0, test_1.expect)(ctx.stdout).to.contain('"label": "Opportunity"');
|
|
147
149
|
});
|
|
148
150
|
test_1.test
|
|
@@ -169,15 +171,203 @@ describe('graph', () => {
|
|
|
169
171
|
.it('treats "getCollection" as a get operation', ctx => {
|
|
170
172
|
(0, test_1.expect)(ctx.stdout).to.contain('"label": "Opportunity"');
|
|
171
173
|
});
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
// --- createItem ---
|
|
175
|
+
test_1.test
|
|
176
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
177
|
+
.stub(global, 'fetch', stub => stub.resolves({
|
|
178
|
+
ok: true,
|
|
179
|
+
status: 201,
|
|
180
|
+
json: () => Promise.resolve({ path: '/sobjects/Task/records/new123' }),
|
|
181
|
+
}))
|
|
182
|
+
.stdout()
|
|
183
|
+
.command(['graph', 'createItem', '--path=/sobjects/Task/records', '--body={"Name":"Test Task"}'])
|
|
184
|
+
.it('createItem sends POST with parsed JSON body', ctx => {
|
|
185
|
+
const fetchStub = global.fetch;
|
|
186
|
+
(0, test_1.expect)(fetchStub.calledOnce).to.be.true;
|
|
187
|
+
const [url, options] = fetchStub.firstCall.args;
|
|
188
|
+
(0, test_1.expect)(url).to.equal('http://localhost:9200/sobjects/Task/records');
|
|
189
|
+
(0, test_1.expect)(options.method).to.equal('POST');
|
|
190
|
+
(0, test_1.expect)(JSON.parse(options.body)).to.deep.equal({ Name: 'Test Task' });
|
|
191
|
+
(0, test_1.expect)(ctx.stdout).to.contain('/sobjects/Task/records/new123');
|
|
192
|
+
});
|
|
193
|
+
test_1.test
|
|
194
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
195
|
+
.command(['graph', 'createItem', '--path=/sobjects/Task/records'])
|
|
196
|
+
.catch(err => {
|
|
197
|
+
(0, test_1.expect)(err.message).to.contain('--body or --body-file is required');
|
|
198
|
+
})
|
|
199
|
+
.it('createItem without --body or --body-file errors');
|
|
200
|
+
test_1.test
|
|
201
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
202
|
+
.command(['graph', 'createItem', '--path=/sobjects/Task/records', '--body=not-json'])
|
|
203
|
+
.catch(err => {
|
|
204
|
+
(0, test_1.expect)(err.message).to.contain('Invalid JSON in --body');
|
|
205
|
+
})
|
|
206
|
+
.it('createItem with invalid JSON in --body errors');
|
|
207
|
+
test_1.test
|
|
208
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
209
|
+
.stub(global, 'fetch', stub => stub.resolves({
|
|
210
|
+
ok: true,
|
|
211
|
+
status: 201,
|
|
212
|
+
json: () => Promise.resolve({ path: '/sobjects/Task/records/new456' }),
|
|
213
|
+
}))
|
|
214
|
+
.stub(fs_1.default.promises, 'readFile', stub => stub.callsFake((...args) => {
|
|
215
|
+
if (args[0] === '/tmp/body.json')
|
|
216
|
+
return Promise.resolve('{"Name":"From File"}');
|
|
217
|
+
return originalReadFile(...args);
|
|
218
|
+
}))
|
|
219
|
+
.stdout()
|
|
220
|
+
.command(['graph', 'createItem', '--path=/sobjects/Task/records', '--body-file=/tmp/body.json'])
|
|
221
|
+
.it('--body-file reads and parses file as request body', () => {
|
|
222
|
+
const fetchStub = global.fetch;
|
|
223
|
+
const [, options] = fetchStub.firstCall.args;
|
|
224
|
+
(0, test_1.expect)(options.method).to.equal('POST');
|
|
225
|
+
(0, test_1.expect)(JSON.parse(options.body)).to.deep.equal({ Name: 'From File' });
|
|
226
|
+
});
|
|
227
|
+
test_1.test
|
|
228
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
229
|
+
.stub(fs_1.default.promises, 'readFile', stub => stub.callsFake((...args) => {
|
|
230
|
+
if (args[0] === '/tmp/bad.json')
|
|
231
|
+
return Promise.resolve('not valid json');
|
|
232
|
+
return originalReadFile(...args);
|
|
233
|
+
}))
|
|
234
|
+
.command(['graph', 'createItem', '--path=/sobjects/Task/records', '--body-file=/tmp/bad.json'])
|
|
235
|
+
.catch(err => {
|
|
236
|
+
(0, test_1.expect)(err.message).to.contain('Invalid JSON in --body-file');
|
|
237
|
+
})
|
|
238
|
+
.it('--body-file with invalid JSON errors');
|
|
239
|
+
test_1.test
|
|
240
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
241
|
+
.stub(fs_1.default.promises, 'readFile', stub => stub.callsFake((...args) => {
|
|
242
|
+
if (args[0] === '/tmp/missing.json')
|
|
243
|
+
return Promise.reject(new Error('ENOENT: no such file'));
|
|
244
|
+
return originalReadFile(...args);
|
|
245
|
+
}))
|
|
246
|
+
.command(['graph', 'createItem', '--path=/sobjects/Task/records', '--body-file=/tmp/missing.json'])
|
|
247
|
+
.catch(err => {
|
|
248
|
+
(0, test_1.expect)(err.message).to.contain('Could not read --body-file');
|
|
249
|
+
})
|
|
250
|
+
.it('--body-file with non-existent file errors');
|
|
251
|
+
// --- updateItem ---
|
|
252
|
+
test_1.test
|
|
253
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
254
|
+
.stub(global, 'fetch', stub => stub.resolves({
|
|
255
|
+
ok: true,
|
|
256
|
+
status: 200,
|
|
257
|
+
json: () => Promise.resolve({ path: '/sobjects/Task/records/abc123' }),
|
|
258
|
+
}))
|
|
259
|
+
.stdout()
|
|
260
|
+
.command(['graph', 'updateItem', '--path=/sobjects/Task/records/abc123', '--body={"Name":"Updated"}'])
|
|
261
|
+
.it('updateItem sends PATCH with parsed JSON body', () => {
|
|
262
|
+
const fetchStub = global.fetch;
|
|
263
|
+
const [url, options] = fetchStub.firstCall.args;
|
|
264
|
+
(0, test_1.expect)(url).to.equal('http://localhost:9200/sobjects/Task/records/abc123');
|
|
265
|
+
(0, test_1.expect)(options.method).to.equal('PATCH');
|
|
266
|
+
(0, test_1.expect)(JSON.parse(options.body)).to.deep.equal({ Name: 'Updated' });
|
|
267
|
+
});
|
|
268
|
+
test_1.test
|
|
269
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
270
|
+
.command(['graph', 'updateItem', '--path=/sobjects/Task/records/abc123'])
|
|
271
|
+
.catch(err => {
|
|
272
|
+
(0, test_1.expect)(err.message).to.contain('--body or --body-file is required');
|
|
273
|
+
})
|
|
274
|
+
.it('updateItem without --body or --body-file errors');
|
|
275
|
+
// --- deleteItem ---
|
|
276
|
+
test_1.test
|
|
277
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
278
|
+
.stub(global, 'fetch', stub => stub.resolves({
|
|
279
|
+
ok: true,
|
|
280
|
+
status: 204,
|
|
281
|
+
json: () => Promise.reject(new Error('No body')),
|
|
282
|
+
}))
|
|
283
|
+
.stdout()
|
|
284
|
+
.command(['graph', 'deleteItem', '--path=/sobjects/Task/records/abc123', '--yes'])
|
|
285
|
+
.it('deleteItem sends DELETE with no body and handles 204', ctx => {
|
|
286
|
+
const fetchStub = global.fetch;
|
|
287
|
+
const [url, options] = fetchStub.firstCall.args;
|
|
288
|
+
(0, test_1.expect)(url).to.equal('http://localhost:9200/sobjects/Task/records/abc123');
|
|
289
|
+
(0, test_1.expect)(options.method).to.equal('DELETE');
|
|
290
|
+
(0, test_1.expect)(options.body).to.be.undefined;
|
|
291
|
+
(0, test_1.expect)(ctx.stdout).to.equal('');
|
|
292
|
+
});
|
|
293
|
+
test_1.test
|
|
294
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
295
|
+
.command(['graph', 'deleteItem', '--path=/sobjects/Task/records/abc123', '--body={"x":1}'])
|
|
296
|
+
.catch(err => {
|
|
297
|
+
(0, test_1.expect)(err.message).to.contain('--body and --body-file are not supported for deleteItem');
|
|
298
|
+
})
|
|
299
|
+
.it('deleteItem rejects --body flag');
|
|
300
|
+
test_1.test
|
|
301
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
302
|
+
.command(['graph', 'deleteItem', '--path=/sobjects/Task/records/abc123', '--body-file=/tmp/body.json'])
|
|
303
|
+
.catch(err => {
|
|
304
|
+
(0, test_1.expect)(err.message).to.contain('--body and --body-file are not supported for deleteItem');
|
|
305
|
+
})
|
|
306
|
+
.it('deleteItem rejects --body-file flag');
|
|
307
|
+
// --- deleteItem confirmation prompt ---
|
|
308
|
+
test_1.test
|
|
309
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
310
|
+
.stub(global, 'fetch', stub => stub.resolves({
|
|
311
|
+
ok: true,
|
|
312
|
+
status: 204,
|
|
313
|
+
json: () => Promise.reject(new Error('No body')),
|
|
314
|
+
}))
|
|
315
|
+
.stub(inquirer_1.default, 'prompt', stub => stub.resolves({ proceed: true }))
|
|
316
|
+
.stdout()
|
|
317
|
+
.command(['graph', 'deleteItem', '--path=/sobjects/Task/records/abc123'])
|
|
318
|
+
.it('deleteItem prompts for confirmation and proceeds when accepted', () => {
|
|
319
|
+
const promptStub = inquirer_1.default.prompt;
|
|
320
|
+
(0, test_1.expect)(promptStub.calledOnce).to.be.true;
|
|
321
|
+
const [question] = promptStub.firstCall.args;
|
|
322
|
+
(0, test_1.expect)(question.message).to.contain('About to DELETE /sobjects/Task/records/abc123');
|
|
323
|
+
const fetchStub = global.fetch;
|
|
324
|
+
(0, test_1.expect)(fetchStub.calledOnce).to.be.true;
|
|
325
|
+
});
|
|
326
|
+
test_1.test
|
|
327
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
328
|
+
.stub(global, 'fetch', stub => stub.resolves({
|
|
329
|
+
ok: true,
|
|
330
|
+
status: 204,
|
|
331
|
+
json: () => Promise.reject(new Error('No body')),
|
|
332
|
+
}))
|
|
333
|
+
.stub(inquirer_1.default, 'prompt', stub => stub.resolves({ proceed: false }))
|
|
334
|
+
.stdout()
|
|
335
|
+
.command(['graph', 'deleteItem', '--path=/sobjects/Task/records/abc123'])
|
|
336
|
+
.it('deleteItem aborts when confirmation is declined', ctx => {
|
|
337
|
+
(0, test_1.expect)(ctx.stdout).to.contain('Aborted.');
|
|
338
|
+
const fetchStub = global.fetch;
|
|
339
|
+
(0, test_1.expect)(fetchStub.called).to.be.false;
|
|
340
|
+
});
|
|
341
|
+
test_1.test
|
|
342
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
343
|
+
.stub(global, 'fetch', stub => stub.resolves({
|
|
344
|
+
ok: true,
|
|
345
|
+
status: 204,
|
|
346
|
+
json: () => Promise.reject(new Error('No body')),
|
|
347
|
+
}))
|
|
348
|
+
.stdout()
|
|
349
|
+
.command(['graph', 'deleteItem', '--path=/sobjects/Task/records/abc123', '--yes'])
|
|
350
|
+
.it('deleteItem --yes bypasses confirmation prompt', () => {
|
|
351
|
+
const fetchStub = global.fetch;
|
|
352
|
+
(0, test_1.expect)(fetchStub.calledOnce).to.be.true;
|
|
353
|
+
});
|
|
354
|
+
// --- GET ignores body ---
|
|
355
|
+
test_1.test
|
|
356
|
+
.stub(Configuration, 'getConfiguration', stub => stub.resolves(MOCK_CONFIG))
|
|
357
|
+
.stub(global, 'fetch', stub => stub.resolves({
|
|
358
|
+
ok: true,
|
|
359
|
+
status: 200,
|
|
360
|
+
json: () => Promise.resolve(MOCK_RESPONSE),
|
|
361
|
+
}))
|
|
362
|
+
.stdout()
|
|
363
|
+
.command(['graph', 'getItem', '--path=/sobjects/Opportunity', '--body={"ignored":true}'])
|
|
364
|
+
.it('getItem operations ignore --body flag', () => {
|
|
365
|
+
const fetchStub = global.fetch;
|
|
366
|
+
const [, options] = fetchStub.firstCall.args;
|
|
367
|
+
(0, test_1.expect)(options.method).to.equal('GET');
|
|
368
|
+
(0, test_1.expect)(options.body).to.be.undefined;
|
|
369
|
+
});
|
|
370
|
+
// --- invalid operation ---
|
|
181
371
|
test_1.test
|
|
182
372
|
.stderr()
|
|
183
373
|
.command(['graph', 'put', '--path=/sobjects/Opportunity'])
|
package/oclif.manifest.json
CHANGED
|
@@ -257,11 +257,10 @@
|
|
|
257
257
|
"aliases": [],
|
|
258
258
|
"args": {
|
|
259
259
|
"operation": {
|
|
260
|
-
"default": "
|
|
260
|
+
"default": "getItem",
|
|
261
261
|
"description": "Operation to perform on the graph path",
|
|
262
262
|
"name": "operation",
|
|
263
263
|
"options": [
|
|
264
|
-
"get",
|
|
265
264
|
"getItem",
|
|
266
265
|
"getCollection",
|
|
267
266
|
"createItem",
|
|
@@ -273,8 +272,11 @@
|
|
|
273
272
|
},
|
|
274
273
|
"description": "Query a running integration graph and print the response",
|
|
275
274
|
"examples": [
|
|
276
|
-
"<%= config.bin %> <%= command.id %> --path=/sobjects/Opportunity/records/abc123",
|
|
277
|
-
"<%= config.bin %> <%= command.id %>
|
|
275
|
+
"<%= config.bin %> <%= command.id %> getItem --path=/sobjects/Opportunity/records/abc123",
|
|
276
|
+
"<%= config.bin %> <%= command.id %> getCollection --path=/sobjects/Opportunity --port=9201",
|
|
277
|
+
"<%= config.bin %> <%= command.id %> createItem --path=/sobjects/Task/records --body='{\"Name\":\"New Task\"}'",
|
|
278
|
+
"<%= config.bin %> <%= command.id %> updateItem --path=/sobjects/Task/records/abc123 --body='{\"Name\":\"Updated\"}'",
|
|
279
|
+
"<%= config.bin %> <%= command.id %> deleteItem --path=/sobjects/Task/records/abc123"
|
|
278
280
|
],
|
|
279
281
|
"flags": {
|
|
280
282
|
"json": {
|
|
@@ -360,6 +362,34 @@
|
|
|
360
362
|
"hasDynamicHelp": false,
|
|
361
363
|
"multiple": false,
|
|
362
364
|
"type": "option"
|
|
365
|
+
},
|
|
366
|
+
"body": {
|
|
367
|
+
"char": "b",
|
|
368
|
+
"description": "JSON request body for createItem/updateItem operations",
|
|
369
|
+
"exclusive": [
|
|
370
|
+
"body-file"
|
|
371
|
+
],
|
|
372
|
+
"name": "body",
|
|
373
|
+
"hasDynamicHelp": false,
|
|
374
|
+
"multiple": false,
|
|
375
|
+
"type": "option"
|
|
376
|
+
},
|
|
377
|
+
"body-file": {
|
|
378
|
+
"description": "Path to a JSON file to use as request body",
|
|
379
|
+
"exclusive": [
|
|
380
|
+
"body"
|
|
381
|
+
],
|
|
382
|
+
"name": "body-file",
|
|
383
|
+
"hasDynamicHelp": false,
|
|
384
|
+
"multiple": false,
|
|
385
|
+
"type": "option"
|
|
386
|
+
},
|
|
387
|
+
"yes": {
|
|
388
|
+
"char": "y",
|
|
389
|
+
"description": "Skip confirmation prompt for destructive operations (deleteItem)",
|
|
390
|
+
"name": "yes",
|
|
391
|
+
"allowNo": false,
|
|
392
|
+
"type": "boolean"
|
|
363
393
|
}
|
|
364
394
|
},
|
|
365
395
|
"hasDynamicHelp": false,
|
|
@@ -969,5 +999,5 @@
|
|
|
969
999
|
]
|
|
970
1000
|
}
|
|
971
1001
|
},
|
|
972
|
-
"version": "0.
|
|
1002
|
+
"version": "1.0.0"
|
|
973
1003
|
}
|