@carthooks/arcubase-cli 0.1.2 → 0.1.4
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/bundle/arcubase-admin.mjs +1238 -16
- package/bundle/arcubase.mjs +1238 -16
- package/dist/bin/arcubase-admin.js +0 -0
- package/dist/bin/arcubase.js +0 -0
- package/dist/runtime/command_registry.d.ts +8 -0
- package/dist/runtime/command_registry.d.ts.map +1 -1
- package/dist/runtime/command_registry.js +35 -0
- package/dist/runtime/entity_save_schema.d.ts +15 -0
- package/dist/runtime/entity_save_schema.d.ts.map +1 -0
- package/dist/runtime/entity_save_schema.js +641 -0
- package/dist/runtime/errors.d.ts +11 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +294 -12
- package/dist/runtime/upload.d.ts +13 -0
- package/dist/runtime/upload.d.ts.map +1 -0
- package/dist/runtime/upload.js +148 -0
- package/dist/runtime/zod_registry.d.ts +6 -0
- package/dist/runtime/zod_registry.d.ts.map +1 -1
- package/dist/runtime/zod_registry.js +136 -0
- package/dist/tests/command_registry.test.js +5 -0
- package/dist/tests/entity_save_schema.test.d.ts +2 -0
- package/dist/tests/entity_save_schema.test.d.ts.map +1 -0
- package/dist/tests/entity_save_schema.test.js +221 -0
- package/dist/tests/execute_validation.test.js +603 -0
- package/dist/tests/help.test.js +49 -0
- package/dist/tests/upload.test.d.ts +2 -0
- package/dist/tests/upload.test.d.ts.map +1 -0
- package/dist/tests/upload.test.js +107 -0
- package/package.json +1 -1
- package/sdk-dist/docs/runtime-reference/README.md +187 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/README.md +120 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/address.md +34 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/boolean.md +37 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/button.md +47 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/checkbox.md +61 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/datasets.md +35 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/datetime.md +40 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/department.md +33 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/departments.md +34 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/divider.md +39 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/file.md +53 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/formula.md +44 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/image.md +52 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/linkto.md +67 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/location.md +32 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/lookup.md +41 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/member.md +37 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/members.md +31 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/number.md +70 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/query.md +50 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/radio.md +53 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/relation.md +55 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/relationfield.md +38 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/rollup.md +54 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/select.md +63 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/selects.md +51 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/serialnumber.md +48 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/signature.md +31 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/status.md +70 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/subform.md +74 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/tags.md +19 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/text.md +76 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/textarea.md +47 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/workflow_status.md +17 -0
- package/sdk-dist/docs/runtime-reference/entity-schema.md +197 -0
- package/sdk-dist/docs/runtime-reference/examples/README.md +51 -0
- package/sdk-dist/docs/runtime-reference/examples/crm-01/README.md +20 -0
- package/sdk-dist/docs/runtime-reference/examples/mes-01/README.md +21 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +183 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/app-overview.md +79 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/create-app.json +3 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/goods-receipt.create-entity.json +3 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/goods-receipt.schema.json +159 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/item-master.create-entity.json +3 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/item-master.schema.json +179 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/picking-list.create-entity.json +3 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/picking-list.schema.json +168 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/purchase-order.create-entity.json +3 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/purchase-order.schema.json +417 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.bulk-update.json +22 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.create-entity.json +3 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.query.json +10 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.row.insert.json +28 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.schema.json +398 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.selection.condition.json +13 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/sales-order.selection.ids.json +7 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/stock-issue.create-entity.json +3 -0
- package/sdk-dist/docs/runtime-reference/examples/oms-01/stock-issue.schema.json +137 -0
- package/sdk-dist/docs/runtime-reference/examples/wms-01/README.md +21 -0
- package/sdk-dist/docs/runtime-reference/row-crud.md +188 -0
- package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +494 -0
- package/sdk-dist/docs/runtime-reference/table-lifecycle.md +135 -0
- package/sdk-dist/docs/runtime-reference/uploads.md +106 -0
- package/sdk-dist/docs/runtime-reference/workflow/README.md +19 -0
- package/src/runtime/command_registry.ts +47 -0
- package/src/runtime/entity_save_schema.ts +708 -0
- package/src/runtime/errors.ts +12 -0
- package/src/runtime/execute.ts +367 -12
- package/src/runtime/upload.ts +196 -0
- package/src/runtime/zod_registry.ts +156 -0
- package/src/tests/command_registry.test.ts +6 -0
- package/src/tests/entity_save_schema.test.ts +236 -0
- package/src/tests/execute_validation.test.ts +781 -0
- package/src/tests/help.test.ts +60 -0
- package/src/tests/upload.test.ts +133 -0
|
@@ -31,6 +31,19 @@ test('admin save entity rejects invalid body file', async () => {
|
|
|
31
31
|
assert.equal(error.details?.requestType, 'EntitySaveReqVO');
|
|
32
32
|
assert.ok((error.details?.issues ?? []).some((item) => item.path.startsWith('body.layout')));
|
|
33
33
|
assert.ok((error.details?.tsHints ?? []).some((item) => item.file.startsWith('/opt/arcubase-sdk/')));
|
|
34
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/entity-schema.md')));
|
|
35
|
+
return true;
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
test('admin create app by tenants rejects name longer than 20 chars', async () => {
|
|
39
|
+
const bodyFile = tempJSONFile({ name: 'CLI Verify Happy Path' });
|
|
40
|
+
await assert.rejects(async () => {
|
|
41
|
+
await executeCLI('admin', ['app', 'create-app-by-tenants', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
42
|
+
}, (error) => {
|
|
43
|
+
assert.ok(error instanceof CLIError);
|
|
44
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
45
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.name'));
|
|
46
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/table-lifecycle.md')));
|
|
34
47
|
return true;
|
|
35
48
|
});
|
|
36
49
|
});
|
|
@@ -47,3 +60,593 @@ test('admin api-prefixed command executes against external root path', async ()
|
|
|
47
60
|
assert.equal(out.kind, 'result');
|
|
48
61
|
assert.equal(out.data.url, 'https://arcubase.example.com/apps/app_1/entity-shares');
|
|
49
62
|
});
|
|
63
|
+
test('admin save entity treats top-level error body as failure even on http 200', async () => {
|
|
64
|
+
const bodyFile = tempJSONFile({
|
|
65
|
+
id: 2188889845,
|
|
66
|
+
app_id: 2188889844,
|
|
67
|
+
name: '订单',
|
|
68
|
+
schema_version: 1,
|
|
69
|
+
field_id_seq: 1002,
|
|
70
|
+
layout: [[1001]],
|
|
71
|
+
fields: [
|
|
72
|
+
{
|
|
73
|
+
id: 1001,
|
|
74
|
+
label: '订单号',
|
|
75
|
+
type: 'text',
|
|
76
|
+
required: true,
|
|
77
|
+
unique: false,
|
|
78
|
+
editable: true,
|
|
79
|
+
visible: true,
|
|
80
|
+
show_label: true,
|
|
81
|
+
scannable: false,
|
|
82
|
+
default_value_mode: 0,
|
|
83
|
+
description: '',
|
|
84
|
+
value: null,
|
|
85
|
+
number_decimal: 0,
|
|
86
|
+
depends: [],
|
|
87
|
+
key: 'order_id',
|
|
88
|
+
code_index: false,
|
|
89
|
+
options: {},
|
|
90
|
+
transfers: null,
|
|
91
|
+
children: null,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
workflow_enabled: false,
|
|
95
|
+
});
|
|
96
|
+
await assert.rejects(async () => {
|
|
97
|
+
await executeCLI('admin', ['entity', 'admin-save-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async () => new Response(JSON.stringify({
|
|
98
|
+
error: {
|
|
99
|
+
message: 'json: cannot unmarshal object into Go struct field .EntityVO.fields of type []models.Field',
|
|
100
|
+
type: 'system',
|
|
101
|
+
key: 'error.system',
|
|
102
|
+
},
|
|
103
|
+
trace_id: 'req_xxx',
|
|
104
|
+
}), { status: 200 }));
|
|
105
|
+
}, (error) => {
|
|
106
|
+
assert.ok(error instanceof CLIError);
|
|
107
|
+
assert.equal(error.code, 'UPSTREAM_BODY_ERROR');
|
|
108
|
+
assert.equal(error.details?.endpoint, '/apps/app_1/entity/entity_1');
|
|
109
|
+
assert.equal(error.details?.method, 'PUT');
|
|
110
|
+
assert.equal(error.details?.traceId, 'req_xxx');
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
test('admin save entity normalizes shell null placeholders before validation', async () => {
|
|
115
|
+
const bodyFile = tempJSONFile({
|
|
116
|
+
id: 2188889845,
|
|
117
|
+
app_id: 2188889844,
|
|
118
|
+
name: '订单',
|
|
119
|
+
schema_version: 0,
|
|
120
|
+
field_id_seq: 1002,
|
|
121
|
+
layout: null,
|
|
122
|
+
current_serial: null,
|
|
123
|
+
fields: [
|
|
124
|
+
{
|
|
125
|
+
id: 1001,
|
|
126
|
+
label: '订单号',
|
|
127
|
+
type: 'text',
|
|
128
|
+
required: true,
|
|
129
|
+
unique: false,
|
|
130
|
+
editable: true,
|
|
131
|
+
visible: true,
|
|
132
|
+
show_label: true,
|
|
133
|
+
scannable: false,
|
|
134
|
+
default_value_mode: 0,
|
|
135
|
+
description: '',
|
|
136
|
+
value: null,
|
|
137
|
+
number_decimal: 0,
|
|
138
|
+
depends: [],
|
|
139
|
+
key: 'order_id',
|
|
140
|
+
code_index: false,
|
|
141
|
+
options: {
|
|
142
|
+
placeholder: '',
|
|
143
|
+
type: 'text',
|
|
144
|
+
lengthLimit: false,
|
|
145
|
+
lengthMin: 0,
|
|
146
|
+
lengthMax: 255,
|
|
147
|
+
},
|
|
148
|
+
transfers: null,
|
|
149
|
+
children: null,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
options: {
|
|
153
|
+
Buttons: null,
|
|
154
|
+
CustomActions: null,
|
|
155
|
+
FrontendEvents: null,
|
|
156
|
+
TitleFormat: {
|
|
157
|
+
Fields: null,
|
|
158
|
+
Parts: null,
|
|
159
|
+
Body: '',
|
|
160
|
+
},
|
|
161
|
+
misc: null,
|
|
162
|
+
},
|
|
163
|
+
workflow_enabled: false,
|
|
164
|
+
});
|
|
165
|
+
const out = await executeCLI('admin', ['entity', 'admin-save-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
166
|
+
assert.equal(out.kind, 'result');
|
|
167
|
+
const requestBody = out.data;
|
|
168
|
+
assert.deepEqual(requestBody.layout, []);
|
|
169
|
+
assert.equal('current_serial' in requestBody, false);
|
|
170
|
+
assert.deepEqual(requestBody.options.Buttons, []);
|
|
171
|
+
assert.deepEqual(requestBody.options.CustomActions, []);
|
|
172
|
+
assert.deepEqual(requestBody.options.FrontendEvents, []);
|
|
173
|
+
assert.deepEqual(requestBody.options.TitleFormat.Fields, []);
|
|
174
|
+
assert.deepEqual(requestBody.options.TitleFormat.Parts, []);
|
|
175
|
+
assert.deepEqual(requestBody.options.misc, {});
|
|
176
|
+
});
|
|
177
|
+
test('delete entity row rejects selection type outside ids', async () => {
|
|
178
|
+
const bodyFile = tempJSONFile({
|
|
179
|
+
selection: {
|
|
180
|
+
type: 'selection',
|
|
181
|
+
ids: [4000000003],
|
|
182
|
+
length: 1,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
await assert.rejects(async () => {
|
|
186
|
+
await executeCLI('user', ['workflow', 'delete-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
187
|
+
}, (error) => {
|
|
188
|
+
assert.ok(error instanceof CLIError);
|
|
189
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
190
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.selection'));
|
|
191
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/search-and-bulk-actions.md')));
|
|
192
|
+
return true;
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
test('bulk update accepts ids selection with number_add action', async () => {
|
|
196
|
+
const bodyFile = tempJSONFile({
|
|
197
|
+
selection: {
|
|
198
|
+
type: 'ids',
|
|
199
|
+
ids: [4000000003],
|
|
200
|
+
length: 1,
|
|
201
|
+
},
|
|
202
|
+
fields: [
|
|
203
|
+
{
|
|
204
|
+
id: 1002,
|
|
205
|
+
action: {
|
|
206
|
+
type: 'number_add',
|
|
207
|
+
number_add: 10,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
const out = await executeCLI('user', ['entity', 'update-entity-bulk', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
213
|
+
assert.equal(out.kind, 'result');
|
|
214
|
+
assert.deepEqual(out.data.selection, { type: 'ids', ids: [4000000003], length: 1 });
|
|
215
|
+
assert.equal(out.data.fields[0].action.type, 'number_add');
|
|
216
|
+
});
|
|
217
|
+
test('selection action accepts condition selection with selectAll', async () => {
|
|
218
|
+
const bodyFile = tempJSONFile({
|
|
219
|
+
selection: {
|
|
220
|
+
type: 'condition',
|
|
221
|
+
length: 1,
|
|
222
|
+
condition: {
|
|
223
|
+
q: 'A-100',
|
|
224
|
+
selectAll: true,
|
|
225
|
+
},
|
|
226
|
+
sorts: [
|
|
227
|
+
{ column: ':1002', order: 'desc' },
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
const out = await executeCLI('user', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
232
|
+
assert.equal(out.kind, 'result');
|
|
233
|
+
assert.equal(out.data.selection.type, 'condition');
|
|
234
|
+
assert.equal(out.data.selection.condition.selectAll, true);
|
|
235
|
+
});
|
|
236
|
+
test('selection action accepts condition selection with filter_* keys', async () => {
|
|
237
|
+
const bodyFile = tempJSONFile({
|
|
238
|
+
selection: {
|
|
239
|
+
type: 'condition',
|
|
240
|
+
length: 1,
|
|
241
|
+
condition: {
|
|
242
|
+
'filter_:1001': '%7B%22is%22%3A%22A-100%22%7D',
|
|
243
|
+
selectAll: true,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
const out = await executeCLI('user', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
248
|
+
assert.equal(out.kind, 'result');
|
|
249
|
+
assert.equal(out.data.selection.type, 'condition');
|
|
250
|
+
assert.equal(out.data.selection.condition.selectAll, true);
|
|
251
|
+
});
|
|
252
|
+
test('selection action accepts condition selection with selectAll only for batch_print', async () => {
|
|
253
|
+
const bodyFile = tempJSONFile({
|
|
254
|
+
selection: {
|
|
255
|
+
type: 'condition',
|
|
256
|
+
length: 1,
|
|
257
|
+
condition: {
|
|
258
|
+
selectAll: true,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
const out = await executeCLI('user', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'batch_print', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
263
|
+
assert.equal(out.kind, 'result');
|
|
264
|
+
assert.equal(out.data.selection.type, 'condition');
|
|
265
|
+
assert.equal(out.data.selection.condition.selectAll, true);
|
|
266
|
+
});
|
|
267
|
+
test('selection action accepts all selection for query-style actions', async () => {
|
|
268
|
+
const bodyFile = tempJSONFile({
|
|
269
|
+
selection: {
|
|
270
|
+
type: 'all',
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
const out = await executeCLI('user', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'query', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
274
|
+
assert.equal(out.kind, 'result');
|
|
275
|
+
assert.equal(out.data.selection.type, 'all');
|
|
276
|
+
});
|
|
277
|
+
test('selection action rejects condition selection for query action', async () => {
|
|
278
|
+
const bodyFile = tempJSONFile({
|
|
279
|
+
selection: {
|
|
280
|
+
type: 'condition',
|
|
281
|
+
length: 1,
|
|
282
|
+
condition: {
|
|
283
|
+
q: 'A-100',
|
|
284
|
+
selectAll: true,
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
await assert.rejects(async () => {
|
|
289
|
+
await executeCLI('user', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'query', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
290
|
+
}, (error) => {
|
|
291
|
+
assert.ok(error instanceof CLIError);
|
|
292
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
293
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.selection.type'));
|
|
294
|
+
return true;
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
test('selection action rejects all selection for archive action', async () => {
|
|
298
|
+
const bodyFile = tempJSONFile({
|
|
299
|
+
selection: {
|
|
300
|
+
type: 'all',
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
await assert.rejects(async () => {
|
|
304
|
+
await executeCLI('user', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
305
|
+
}, (error) => {
|
|
306
|
+
assert.ok(error instanceof CLIError);
|
|
307
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
308
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.selection.type'));
|
|
309
|
+
return true;
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
test('selection action rejects selectAll-only condition for archive action', async () => {
|
|
313
|
+
const bodyFile = tempJSONFile({
|
|
314
|
+
selection: {
|
|
315
|
+
type: 'condition',
|
|
316
|
+
length: 1,
|
|
317
|
+
condition: {
|
|
318
|
+
selectAll: true,
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
await assert.rejects(async () => {
|
|
323
|
+
await executeCLI('user', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
324
|
+
}, (error) => {
|
|
325
|
+
assert.ok(error instanceof CLIError);
|
|
326
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
327
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.selection.condition'));
|
|
328
|
+
return true;
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
test('insert entity rejects local file path for file field and points to arcubase upload', async () => {
|
|
332
|
+
const bodyFile = tempJSONFile({
|
|
333
|
+
fields: {
|
|
334
|
+
1001: 'Lead A',
|
|
335
|
+
1007: './contract.pdf',
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
const calls = [];
|
|
339
|
+
await assert.rejects(async () => {
|
|
340
|
+
await executeCLI('user', ['workflow', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
341
|
+
calls.push({ url: String(url), method: init?.method });
|
|
342
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
343
|
+
return new Response(JSON.stringify({
|
|
344
|
+
data: {
|
|
345
|
+
id: 1,
|
|
346
|
+
app_id: 1,
|
|
347
|
+
fields: [
|
|
348
|
+
{ id: 1001, type: 'text', label: 'Lead Name' },
|
|
349
|
+
{ id: 1007, type: 'file', label: 'Contract File' },
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
}), { status: 200 });
|
|
353
|
+
}
|
|
354
|
+
return new Response('{}', { status: 200 });
|
|
355
|
+
});
|
|
356
|
+
}, (error) => {
|
|
357
|
+
assert.ok(error instanceof CLIError);
|
|
358
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
359
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.fields.1007'));
|
|
360
|
+
assert.match(error.message, /arcubase upload \.\/contract\.pdf/);
|
|
361
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/uploads.md')));
|
|
362
|
+
return true;
|
|
363
|
+
});
|
|
364
|
+
assert.deepEqual(calls, [
|
|
365
|
+
{ url: 'https://arcubase.example.com/entity/app_1/entity_1', method: 'GET' },
|
|
366
|
+
]);
|
|
367
|
+
});
|
|
368
|
+
test('update row rejects object value for image field and requires upload array', async () => {
|
|
369
|
+
const bodyFile = tempJSONFile({
|
|
370
|
+
changed_fields: [1008],
|
|
371
|
+
data: {
|
|
372
|
+
1008: {
|
|
373
|
+
upload_id: 123456,
|
|
374
|
+
file: 'lead-photo.jpg',
|
|
375
|
+
file_name: 'lead-photo.jpg',
|
|
376
|
+
meta: {},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
await assert.rejects(async () => {
|
|
381
|
+
await executeCLI('user', ['workflow', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
382
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
383
|
+
return new Response(JSON.stringify({
|
|
384
|
+
data: {
|
|
385
|
+
id: 1,
|
|
386
|
+
app_id: 1,
|
|
387
|
+
fields: [
|
|
388
|
+
{ id: 1008, type: 'image', label: 'Lead Photo' },
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
}), { status: 200 });
|
|
392
|
+
}
|
|
393
|
+
return new Response('{}', { status: 200 });
|
|
394
|
+
});
|
|
395
|
+
}, (error) => {
|
|
396
|
+
assert.ok(error instanceof CLIError);
|
|
397
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
398
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.data.1008'));
|
|
399
|
+
assert.match(error.message, /must be an array/);
|
|
400
|
+
assert.match(error.message, /arcubase upload <local-file>/);
|
|
401
|
+
return true;
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
test('insert entity accepts upload array for file field after schema preflight', async () => {
|
|
405
|
+
const bodyFile = tempJSONFile({
|
|
406
|
+
fields: {
|
|
407
|
+
1001: 'Lead A',
|
|
408
|
+
1007: [
|
|
409
|
+
{
|
|
410
|
+
upload_id: 123456,
|
|
411
|
+
file: 'contract.pdf',
|
|
412
|
+
file_name: 'contract.pdf',
|
|
413
|
+
meta: {},
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
const calls = [];
|
|
419
|
+
const out = await executeCLI('user', ['workflow', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
420
|
+
calls.push({ url: String(url), method: init?.method });
|
|
421
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
422
|
+
return new Response(JSON.stringify({
|
|
423
|
+
data: {
|
|
424
|
+
id: 1,
|
|
425
|
+
app_id: 1,
|
|
426
|
+
fields: [
|
|
427
|
+
{ id: 1001, type: 'text', label: 'Lead Name' },
|
|
428
|
+
{ id: 1007, type: 'file', label: 'Contract File' },
|
|
429
|
+
],
|
|
430
|
+
},
|
|
431
|
+
}), { status: 200 });
|
|
432
|
+
}
|
|
433
|
+
return new Response(String(init?.body ?? '{}'), { status: 200 });
|
|
434
|
+
});
|
|
435
|
+
assert.equal(out.kind, 'result');
|
|
436
|
+
assert.deepEqual(calls, [
|
|
437
|
+
{ url: 'https://arcubase.example.com/entity/app_1/entity_1', method: 'GET' },
|
|
438
|
+
{ url: 'https://arcubase.example.com/entity/app_1/entity_1/insert', method: 'POST' },
|
|
439
|
+
]);
|
|
440
|
+
assert.equal(out.data.fields['1007'][0].upload_id, 123456);
|
|
441
|
+
});
|
|
442
|
+
test('insert entity decorates upload bind failures with actionable guidance', async () => {
|
|
443
|
+
const bodyFile = tempJSONFile({
|
|
444
|
+
fields: {
|
|
445
|
+
1001: 'Lead A',
|
|
446
|
+
1007: [
|
|
447
|
+
{
|
|
448
|
+
upload_id: 123456,
|
|
449
|
+
file: 'contract.pdf',
|
|
450
|
+
file_name: 'contract.pdf',
|
|
451
|
+
meta: {},
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
await assert.rejects(async () => {
|
|
457
|
+
await executeCLI('user', ['workflow', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
458
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
459
|
+
return new Response(JSON.stringify({
|
|
460
|
+
data: {
|
|
461
|
+
fields: [{ id: 1007, type: 'file', label: 'Contract File' }],
|
|
462
|
+
},
|
|
463
|
+
}), { status: 200 });
|
|
464
|
+
}
|
|
465
|
+
return new Response(JSON.stringify({
|
|
466
|
+
error: {
|
|
467
|
+
message: 'failed to bind upload to assets: Head \"http://test-bucket.test.aliyuncs.com/upload/123456/contract.pdf\": dial tcp: lookup test-bucket.test.aliyuncs.com: no such host',
|
|
468
|
+
type: 'system',
|
|
469
|
+
key: 'error.system',
|
|
470
|
+
},
|
|
471
|
+
trace_id: 'req_upload_bind',
|
|
472
|
+
}), { status: 200 });
|
|
473
|
+
});
|
|
474
|
+
}, (error) => {
|
|
475
|
+
assert.ok(error instanceof CLIError);
|
|
476
|
+
assert.equal(error.code, 'UPSTREAM_BODY_ERROR');
|
|
477
|
+
assert.equal(error.details?.traceId, 'req_upload_bind');
|
|
478
|
+
assert.equal(error.details?.reason, 'the row payload used a file/image upload entry, but Arcubase could not bind that upload into an asset record');
|
|
479
|
+
assert.match(error.details?.hint ?? '', /rerun arcubase upload/);
|
|
480
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/uploads.md')));
|
|
481
|
+
return true;
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
test('selection action rejects condition selection without selectAll', async () => {
|
|
485
|
+
const bodyFile = tempJSONFile({
|
|
486
|
+
selection: {
|
|
487
|
+
type: 'condition',
|
|
488
|
+
length: 1,
|
|
489
|
+
condition: {
|
|
490
|
+
q: 'A-100',
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
await assert.rejects(async () => {
|
|
495
|
+
await executeCLI('user', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
496
|
+
}, (error) => {
|
|
497
|
+
assert.ok(error instanceof CLIError);
|
|
498
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
499
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.selection.condition.selectAll'));
|
|
500
|
+
return true;
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
test('selection action rejects condition selection that uses text_search instead of q/filter keys', async () => {
|
|
504
|
+
const bodyFile = tempJSONFile({
|
|
505
|
+
selection: {
|
|
506
|
+
type: 'condition',
|
|
507
|
+
length: 1,
|
|
508
|
+
condition: {
|
|
509
|
+
text_search: 'A-100',
|
|
510
|
+
selectAll: true,
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
await assert.rejects(async () => {
|
|
515
|
+
await executeCLI('user', ['workflow', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
516
|
+
}, (error) => {
|
|
517
|
+
assert.ok(error instanceof CLIError);
|
|
518
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
519
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.selection.condition'));
|
|
520
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/search-and-bulk-actions.md')));
|
|
521
|
+
return true;
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
test('bulk update accepts condition selection with q', async () => {
|
|
525
|
+
const bodyFile = tempJSONFile({
|
|
526
|
+
selection: {
|
|
527
|
+
type: 'condition',
|
|
528
|
+
length: 1,
|
|
529
|
+
condition: {
|
|
530
|
+
q: 'A-100',
|
|
531
|
+
selectAll: true,
|
|
532
|
+
},
|
|
533
|
+
sorts: [
|
|
534
|
+
{ column: ':1002', order: 'desc' },
|
|
535
|
+
],
|
|
536
|
+
},
|
|
537
|
+
fields: [
|
|
538
|
+
{
|
|
539
|
+
id: 1002,
|
|
540
|
+
action: {
|
|
541
|
+
type: 'number_add',
|
|
542
|
+
number_add: 2,
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
});
|
|
547
|
+
const out = await executeCLI('user', ['entity', 'update-entity-bulk', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
548
|
+
assert.equal(out.kind, 'result');
|
|
549
|
+
assert.equal(out.data.selection.type, 'condition');
|
|
550
|
+
assert.equal(out.data.fields[0].action.type, 'number_add');
|
|
551
|
+
});
|
|
552
|
+
test('entity tags get-selection-tags accepts ids selection', async () => {
|
|
553
|
+
const bodyFile = tempJSONFile({
|
|
554
|
+
policy_id: '',
|
|
555
|
+
selection: {
|
|
556
|
+
type: 'ids',
|
|
557
|
+
ids: [4000000003],
|
|
558
|
+
length: 1,
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
const out = await executeCLI('admin', ['entity-tags', 'get-selection-tags', '--appId', 'app_1', '--entityId', 'entity_1', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
562
|
+
assert.equal(out.kind, 'result');
|
|
563
|
+
assert.equal(out.data.selection.type, 'ids');
|
|
564
|
+
});
|
|
565
|
+
test('entity tags batch-add accepts tag_names and condition selection', async () => {
|
|
566
|
+
const bodyFile = tempJSONFile({
|
|
567
|
+
policy_id: '',
|
|
568
|
+
selection: {
|
|
569
|
+
type: 'condition',
|
|
570
|
+
length: 1,
|
|
571
|
+
condition: {
|
|
572
|
+
q: 'A-100',
|
|
573
|
+
selectAll: true,
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
tag_names: ['tag-a'],
|
|
577
|
+
});
|
|
578
|
+
const out = await executeCLI('admin', ['entity-tags', 'batch-add-entity-tags', '--appId', 'app_1', '--entityId', 'entity_1', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
579
|
+
assert.equal(out.kind, 'result');
|
|
580
|
+
assert.equal(out.data.tag_names[0], 'tag-a');
|
|
581
|
+
});
|
|
582
|
+
test('invoke batch operator accepts backend request shape', async () => {
|
|
583
|
+
const bodyFile = tempJSONFile({
|
|
584
|
+
id: 'event_1',
|
|
585
|
+
data: {
|
|
586
|
+
action_id: 'action_1',
|
|
587
|
+
policy_id: '',
|
|
588
|
+
selection: {
|
|
589
|
+
type: 'all',
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
params: {},
|
|
593
|
+
});
|
|
594
|
+
const out = await executeCLI('user', ['entity', 'invoke-entity-batch-operator', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (_url, init) => new Response(String(init?.body ?? '{}'), { status: 200 }));
|
|
595
|
+
assert.equal(out.kind, 'result');
|
|
596
|
+
assert.equal(out.data.id, 'event_1');
|
|
597
|
+
assert.equal(out.data.data.action_id, 'action_1');
|
|
598
|
+
assert.equal(out.data.data.selection.type, 'all');
|
|
599
|
+
});
|
|
600
|
+
test('query entity rejects camelCase viewMode and points to row crud doc', async () => {
|
|
601
|
+
const bodyFile = tempJSONFile({
|
|
602
|
+
limit: 20,
|
|
603
|
+
offset: 0,
|
|
604
|
+
viewMode: 'grid',
|
|
605
|
+
});
|
|
606
|
+
await assert.rejects(async () => {
|
|
607
|
+
await executeCLI('user', ['workflow', 'query-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
608
|
+
}, (error) => {
|
|
609
|
+
assert.ok(error instanceof CLIError);
|
|
610
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
611
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.viewMode'));
|
|
612
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/row-crud.md')));
|
|
613
|
+
return true;
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
test('update entity row rejects fields key', async () => {
|
|
617
|
+
const bodyFile = tempJSONFile({
|
|
618
|
+
fields: {
|
|
619
|
+
1002: 99,
|
|
620
|
+
},
|
|
621
|
+
data: {
|
|
622
|
+
'1002': 99,
|
|
623
|
+
},
|
|
624
|
+
changed_fields: [1002],
|
|
625
|
+
});
|
|
626
|
+
await assert.rejects(async () => {
|
|
627
|
+
await executeCLI('user', ['workflow', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
628
|
+
}, (error) => {
|
|
629
|
+
assert.ok(error instanceof CLIError);
|
|
630
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
631
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.fields'));
|
|
632
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/row-crud.md')));
|
|
633
|
+
return true;
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
test('update entity row rejects changed_fields that are missing from data', async () => {
|
|
637
|
+
const bodyFile = tempJSONFile({
|
|
638
|
+
data: {
|
|
639
|
+
'1002': 99,
|
|
640
|
+
},
|
|
641
|
+
changed_fields: [1003],
|
|
642
|
+
});
|
|
643
|
+
await assert.rejects(async () => {
|
|
644
|
+
await executeCLI('user', ['workflow', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
645
|
+
}, (error) => {
|
|
646
|
+
assert.ok(error instanceof CLIError);
|
|
647
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
648
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.changed_fields.0'));
|
|
649
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/row-crud.md')));
|
|
650
|
+
return true;
|
|
651
|
+
});
|
|
652
|
+
});
|
package/dist/tests/help.test.js
CHANGED
|
@@ -15,14 +15,63 @@ test('root help prints modules', async () => {
|
|
|
15
15
|
assert.equal(out.kind, 'help');
|
|
16
16
|
assert.match(out.text, /app/);
|
|
17
17
|
});
|
|
18
|
+
test('root help does not require runtime env', async () => {
|
|
19
|
+
const out = await executeCLI('admin', ['--help'], undefined, async () => new Response('{}'));
|
|
20
|
+
assert.equal(out.kind, 'help');
|
|
21
|
+
assert.match(out.text, /modules:/);
|
|
22
|
+
});
|
|
18
23
|
test('module help prints create-entity command', async () => {
|
|
19
24
|
const out = await executeCLI('admin', ['app', '--help'], env, async () => new Response('{}'));
|
|
20
25
|
assert.equal(out.kind, 'help');
|
|
21
26
|
assert.match(out.text, /create-entity/);
|
|
22
27
|
});
|
|
28
|
+
test('module help does not require runtime env', async () => {
|
|
29
|
+
const out = await executeCLI('user', ['workflow', '--help'], undefined, async () => new Response('{}'));
|
|
30
|
+
assert.equal(out.kind, 'help');
|
|
31
|
+
assert.match(out.text, /delete-entity-row/);
|
|
32
|
+
});
|
|
23
33
|
test('command help prints normalized external endpoint', async () => {
|
|
24
34
|
const out = await executeCLI('admin', ['app-entity-share', 'get-app-entity-shares', '--help'], env, async () => new Response('{}'));
|
|
25
35
|
assert.equal(out.kind, 'help');
|
|
26
36
|
assert.match(out.text, /endpoint: \/apps\/:app_id\/entity-shares/);
|
|
27
37
|
assert.doesNotMatch(out.text, /endpoint: \/api\//);
|
|
28
38
|
});
|
|
39
|
+
test('command help does not require runtime env', async () => {
|
|
40
|
+
const out = await executeCLI('user', ['workflow', 'delete-entity-row', '--help'], undefined, async () => new Response('{}'));
|
|
41
|
+
assert.equal(out.kind, 'help');
|
|
42
|
+
assert.match(out.text, /endpoint: \/entity\/:app_id\/:entity_id\/delete-item/);
|
|
43
|
+
});
|
|
44
|
+
test('command help prints runtime doc hints for request body commands', async () => {
|
|
45
|
+
const out = await executeCLI('user', ['workflow', 'update-entity-row', '--help'], undefined, async () => new Response('{}'));
|
|
46
|
+
assert.equal(out.kind, 'help');
|
|
47
|
+
assert.match(out.text, /docs:/);
|
|
48
|
+
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/row-crud\.md/);
|
|
49
|
+
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/README\.md/);
|
|
50
|
+
});
|
|
51
|
+
test('command help prints query-file support', async () => {
|
|
52
|
+
const out = await executeCLI('user', ['workflow', 'query-entity', '--help'], undefined, async () => new Response('{}'));
|
|
53
|
+
assert.equal(out.kind, 'help');
|
|
54
|
+
assert.match(out.text, /query flags: --query-file/);
|
|
55
|
+
});
|
|
56
|
+
test('command help without request body can still point to lifecycle and examples docs', async () => {
|
|
57
|
+
const out = await executeCLI('admin', ['entity', 'admin-get-entity-info', '--help'], undefined, async () => new Response('{}'));
|
|
58
|
+
assert.equal(out.kind, 'help');
|
|
59
|
+
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/table-lifecycle\.md/);
|
|
60
|
+
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/README\.md/);
|
|
61
|
+
});
|
|
62
|
+
test('admin-save-entity help prints row-operation next step', async () => {
|
|
63
|
+
const out = await executeCLI('admin', ['entity', 'admin-save-entity', '--help'], undefined, async () => new Response('{}'));
|
|
64
|
+
assert.equal(out.kind, 'help');
|
|
65
|
+
assert.match(out.text, /switch to arcubase for row operations/);
|
|
66
|
+
assert.match(out.text, /arcubase workflow insert-entity/);
|
|
67
|
+
assert.match(out.text, /arcubase workflow query-entity/);
|
|
68
|
+
});
|
|
69
|
+
test('wrong admin workflow command points to arcubase row command', async () => {
|
|
70
|
+
await assert.rejects(() => executeCLI('admin', ['workflow', 'insert-entity'], env, async () => new Response('{}')), (error) => {
|
|
71
|
+
assert.equal(error.code, 'UNKNOWN_COMMAND');
|
|
72
|
+
assert.equal(error.details?.reason, 'workflow insert-entity is a row operation, not an arcubase-admin command');
|
|
73
|
+
assert.equal(error.details?.hint, 'use: arcubase workflow insert-entity');
|
|
74
|
+
assert.ok((error.details?.suggestions ?? []).includes('arcubase workflow insert-entity'));
|
|
75
|
+
return true;
|
|
76
|
+
});
|
|
77
|
+
});
|