@carthooks/arcubase-cli 0.1.3 → 0.1.5
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 +546 -243
- package/bundle/arcubase.mjs +546 -243
- package/dist/bin/arcubase-admin.js +0 -0
- package/dist/bin/arcubase.js +0 -0
- package/dist/generated/command_registry.generated.d.ts +36 -112
- package/dist/generated/command_registry.generated.d.ts.map +1 -1
- package/dist/generated/command_registry.generated.js +40 -163
- package/dist/generated/type_index.generated.d.ts +0 -12
- package/dist/generated/type_index.generated.d.ts.map +1 -1
- package/dist/generated/type_index.generated.js +0 -12
- package/dist/generated/zod_registry.generated.d.ts +1 -19
- package/dist/generated/zod_registry.generated.d.ts.map +1 -1
- package/dist/generated/zod_registry.generated.js +0 -21
- package/dist/runtime/command_registry.d.ts +19 -1
- package/dist/runtime/command_registry.d.ts.map +1 -1
- package/dist/runtime/command_registry.js +19 -1
- package/dist/runtime/errors.d.ts +1 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/execute.d.ts.map +1 -1
- package/dist/runtime/execute.js +321 -8
- 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.map +1 -1
- package/dist/runtime/zod_registry.js +50 -30
- package/dist/tests/command_registry.test.js +11 -4
- package/dist/tests/execute_validation.test.js +221 -13
- package/dist/tests/help.test.js +29 -6
- 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/api/admin/app.ts +8 -1
- package/sdk-dist/docs/runtime-reference/README.md +33 -15
- package/sdk-dist/docs/runtime-reference/app-discovery.md +68 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/README.md +2 -2
- package/sdk-dist/docs/runtime-reference/entity-schema/file.md +9 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/image.md +9 -0
- package/sdk-dist/docs/runtime-reference/entity-schema/subform.md +2 -2
- package/sdk-dist/docs/runtime-reference/entity-schema.md +2 -2
- package/sdk-dist/docs/runtime-reference/examples/README.md +1 -1
- package/sdk-dist/docs/runtime-reference/examples/oms-01/README.md +23 -4
- package/sdk-dist/docs/runtime-reference/examples/oms-01/app-overview.md +1 -1
- package/sdk-dist/docs/runtime-reference/row-crud.md +8 -6
- package/sdk-dist/docs/runtime-reference/search-and-bulk-actions.md +5 -5
- package/sdk-dist/docs/runtime-reference/table-lifecycle.md +34 -6
- package/sdk-dist/docs/runtime-reference/uploads.md +106 -0
- package/sdk-dist/generated/command_registry.generated.ts +40 -163
- package/sdk-dist/generated/type_index.generated.ts +0 -12
- package/sdk-dist/generated/zod_registry.generated.ts +0 -36
- package/sdk-dist/types/app.ts +7 -0
- package/src/generated/command_registry.generated.ts +40 -163
- package/src/generated/type_index.generated.ts +0 -12
- package/src/generated/zod_registry.generated.ts +0 -36
- package/src/runtime/command_registry.ts +38 -2
- package/src/runtime/errors.ts +1 -0
- package/src/runtime/execute.ts +368 -8
- package/src/runtime/upload.ts +196 -0
- package/src/runtime/zod_registry.ts +50 -30
- package/src/tests/command_registry.test.ts +13 -4
- package/src/tests/execute_validation.test.ts +257 -13
- package/src/tests/help.test.ts +35 -6
- package/src/tests/upload.test.ts +133 -0
- package/sdk-dist/docs/runtime-reference/workflow/README.md +0 -19
|
@@ -42,54 +42,59 @@ const examplesIndexDocHint = {
|
|
|
42
42
|
title: 'Examples index',
|
|
43
43
|
file: '/opt/arcubase-sdk/docs/runtime-reference/examples/README.md',
|
|
44
44
|
};
|
|
45
|
-
const
|
|
46
|
-
title: '
|
|
47
|
-
file: '/opt/arcubase-sdk/docs/runtime-reference/
|
|
45
|
+
const appDiscoveryDocHint = {
|
|
46
|
+
title: 'App discovery',
|
|
47
|
+
file: '/opt/arcubase-sdk/docs/runtime-reference/app-discovery.md',
|
|
48
48
|
};
|
|
49
49
|
const docHintIndex = {
|
|
50
50
|
AppCreateByTenantsReqVO: [
|
|
51
|
+
appDiscoveryDocHint,
|
|
51
52
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
52
53
|
examplesIndexDocHint,
|
|
53
|
-
omsExampleDocHint,
|
|
54
54
|
],
|
|
55
55
|
AppCreateEntityReqVO: [
|
|
56
|
+
appDiscoveryDocHint,
|
|
56
57
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
57
58
|
examplesIndexDocHint,
|
|
58
|
-
omsExampleDocHint,
|
|
59
59
|
],
|
|
60
60
|
EntitySaveReqVO: [
|
|
61
61
|
{ title: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
|
|
62
|
-
|
|
62
|
+
examplesIndexDocHint,
|
|
63
63
|
],
|
|
64
64
|
EntityQueryReqVO: [
|
|
65
65
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
66
66
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
67
|
-
|
|
67
|
+
examplesIndexDocHint,
|
|
68
68
|
],
|
|
69
69
|
EntityUpdateReqVO: [
|
|
70
70
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
71
|
-
|
|
71
|
+
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
72
|
+
examplesIndexDocHint,
|
|
72
73
|
],
|
|
73
74
|
EntitySelectionActionReqVO: [
|
|
74
75
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
75
|
-
|
|
76
|
+
examplesIndexDocHint,
|
|
76
77
|
],
|
|
77
78
|
InsertReqVO: [
|
|
78
79
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
79
|
-
|
|
80
|
+
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
81
|
+
examplesIndexDocHint,
|
|
80
82
|
],
|
|
81
83
|
EntityBulkUpdateReqVO: [
|
|
82
84
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
83
|
-
|
|
85
|
+
examplesIndexDocHint,
|
|
84
86
|
],
|
|
85
87
|
'EntityInvokeBatchOperatorReqVO & InvokeRequestVO': [
|
|
86
88
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
89
|
+
examplesIndexDocHint,
|
|
87
90
|
],
|
|
88
91
|
'{ policy_id: string selection: any }': [
|
|
89
92
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
93
|
+
examplesIndexDocHint,
|
|
90
94
|
],
|
|
91
95
|
'{ policy_id: string selection: any tag_names: string[] }': [
|
|
92
96
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
97
|
+
examplesIndexDocHint,
|
|
93
98
|
],
|
|
94
99
|
};
|
|
95
100
|
export function getTypeHints(typeName) {
|
|
@@ -126,53 +131,68 @@ export function getDocHints(typeName) {
|
|
|
126
131
|
}
|
|
127
132
|
const commandDocHintIndex = {
|
|
128
133
|
'admin.app.createAppByTenants': [
|
|
134
|
+
appDiscoveryDocHint,
|
|
129
135
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
130
136
|
examplesIndexDocHint,
|
|
131
|
-
|
|
137
|
+
],
|
|
138
|
+
'admin.app.listApps': [
|
|
139
|
+
appDiscoveryDocHint,
|
|
140
|
+
examplesIndexDocHint,
|
|
141
|
+
],
|
|
142
|
+
'admin.app.inventory': [
|
|
143
|
+
appDiscoveryDocHint,
|
|
144
|
+
examplesIndexDocHint,
|
|
132
145
|
],
|
|
133
146
|
'admin.app.createEntity': [
|
|
147
|
+
appDiscoveryDocHint,
|
|
134
148
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
135
|
-
|
|
149
|
+
examplesIndexDocHint,
|
|
136
150
|
],
|
|
137
151
|
'admin.entity.adminGetEntityInfo': [
|
|
152
|
+
appDiscoveryDocHint,
|
|
138
153
|
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
139
|
-
|
|
154
|
+
examplesIndexDocHint,
|
|
140
155
|
],
|
|
141
156
|
'admin.entity.adminSaveEntity': [
|
|
142
157
|
{ title: 'Entity schema', file: '/opt/arcubase-sdk/docs/runtime-reference/entity-schema.md' },
|
|
143
|
-
|
|
158
|
+
{ title: 'Table lifecycle', file: '/opt/arcubase-sdk/docs/runtime-reference/table-lifecycle.md' },
|
|
159
|
+
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
160
|
+
examplesIndexDocHint,
|
|
144
161
|
],
|
|
145
|
-
'user.
|
|
162
|
+
'user.rows.insertEntity': [
|
|
146
163
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
147
|
-
|
|
164
|
+
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
165
|
+
examplesIndexDocHint,
|
|
148
166
|
],
|
|
149
|
-
'user.
|
|
167
|
+
'user.rows.queryEntity': [
|
|
150
168
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
151
|
-
|
|
169
|
+
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
170
|
+
examplesIndexDocHint,
|
|
152
171
|
],
|
|
153
|
-
'user.
|
|
172
|
+
'user.rows.getEntityRow': [
|
|
154
173
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
155
|
-
|
|
174
|
+
examplesIndexDocHint,
|
|
156
175
|
],
|
|
157
|
-
'user.
|
|
176
|
+
'user.rows.updateEntityRow': [
|
|
158
177
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
159
|
-
|
|
178
|
+
{ title: 'Uploads', file: '/opt/arcubase-sdk/docs/runtime-reference/uploads.md' },
|
|
179
|
+
examplesIndexDocHint,
|
|
160
180
|
],
|
|
161
|
-
'user.
|
|
181
|
+
'user.rows.queryEntitySelection': [
|
|
162
182
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
163
|
-
|
|
183
|
+
examplesIndexDocHint,
|
|
164
184
|
],
|
|
165
|
-
'user.
|
|
185
|
+
'user.rows.deleteEntityRow': [
|
|
166
186
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
167
|
-
|
|
187
|
+
examplesIndexDocHint,
|
|
168
188
|
],
|
|
169
|
-
'user.
|
|
189
|
+
'user.rows.restoreEntityRow': [
|
|
170
190
|
{ title: 'Row CRUD', file: '/opt/arcubase-sdk/docs/runtime-reference/row-crud.md' },
|
|
171
|
-
|
|
191
|
+
examplesIndexDocHint,
|
|
172
192
|
],
|
|
173
193
|
'user.entity.updateEntityBulk': [
|
|
174
194
|
{ title: 'Search and bulk actions', file: '/opt/arcubase-sdk/docs/runtime-reference/search-and-bulk-actions.md' },
|
|
175
|
-
|
|
195
|
+
examplesIndexDocHint,
|
|
176
196
|
],
|
|
177
197
|
};
|
|
178
198
|
export function getCommandDocHints(operation) {
|
|
@@ -5,8 +5,15 @@ import { findCommandSuggestions } from '../runtime/command_registry.js';
|
|
|
5
5
|
test('admin app create-entity command exists', () => {
|
|
6
6
|
assert.ok(adminCommands.some((c) => c.commandPath.join(' ') === 'app create-entity'));
|
|
7
7
|
});
|
|
8
|
-
test('
|
|
9
|
-
assert.ok(
|
|
8
|
+
test('admin app list-apps command exists', () => {
|
|
9
|
+
assert.ok(adminCommands.some((c) => c.commandPath.join(' ') === 'app list-apps'));
|
|
10
|
+
});
|
|
11
|
+
test('admin app inventory command exists', () => {
|
|
12
|
+
const suggestions = findCommandSuggestions('app', 'inventory', 20);
|
|
13
|
+
assert.ok(suggestions.some((item) => item.binary === 'arcubase-admin' && item.moduleName === 'app' && item.commandName === 'inventory'));
|
|
14
|
+
});
|
|
15
|
+
test('user rows query-entity command exists', () => {
|
|
16
|
+
assert.ok(userCommands.some((c) => c.commandPath.join(' ') === 'rows query-entity'));
|
|
10
17
|
});
|
|
11
18
|
test('inline request type keeps full object text instead of truncated brace', () => {
|
|
12
19
|
const command = adminCommands.find((c) => c.commandPath.join(' ') === 'dashboard clone-dashboard');
|
|
@@ -21,7 +28,7 @@ test('tag commands no longer emit truncated inline request type', () => {
|
|
|
21
28
|
assert.match(String(typed.requestType), /^\{\s*policy_id:\s*string.*selection:\s*any.*tag_names:\s*string\[\]\s*\}$/);
|
|
22
29
|
assert.equal(typed.scalarParams.length, 0);
|
|
23
30
|
});
|
|
24
|
-
test('suggestions can point wrong admin delete guess to user
|
|
31
|
+
test('suggestions can point wrong admin delete guess to user rows delete command', () => {
|
|
25
32
|
const suggestions = findCommandSuggestions('entity', 'admin-delete-entity-row');
|
|
26
|
-
assert.ok(suggestions.some((item) => item.binary === 'arcubase' && item.moduleName === '
|
|
33
|
+
assert.ok(suggestions.some((item) => item.binary === 'arcubase' && item.moduleName === 'rows' && item.commandName === 'delete-entity-row'));
|
|
27
34
|
});
|
|
@@ -60,6 +60,61 @@ test('admin api-prefixed command executes against external root path', async ()
|
|
|
60
60
|
assert.equal(out.kind, 'result');
|
|
61
61
|
assert.equal(out.data.url, 'https://arcubase.example.com/apps/app_1/entity-shares');
|
|
62
62
|
});
|
|
63
|
+
test('app inventory returns apps with entity lists', async () => {
|
|
64
|
+
const calls = [];
|
|
65
|
+
const out = await executeCLI('admin', ['app', 'inventory'], env, async (url, init) => {
|
|
66
|
+
calls.push(`${init?.method ?? 'GET'} ${String(url)}`);
|
|
67
|
+
if (String(url) === 'https://arcubase.example.com/apps') {
|
|
68
|
+
return new Response(JSON.stringify({
|
|
69
|
+
data: [
|
|
70
|
+
{ id: 101, name: 'Workspace' },
|
|
71
|
+
{ id: 102, name: 'CRM' },
|
|
72
|
+
],
|
|
73
|
+
}), { status: 200 });
|
|
74
|
+
}
|
|
75
|
+
if (String(url) === 'https://arcubase.example.com/apps/101/entities') {
|
|
76
|
+
return new Response(JSON.stringify({
|
|
77
|
+
data: [
|
|
78
|
+
{ id: 201, name: 'Orders' },
|
|
79
|
+
{ id: 202, name: 'Customers' },
|
|
80
|
+
],
|
|
81
|
+
}), { status: 200 });
|
|
82
|
+
}
|
|
83
|
+
if (String(url) === 'https://arcubase.example.com/apps/102/entities') {
|
|
84
|
+
return new Response(JSON.stringify({
|
|
85
|
+
data: [
|
|
86
|
+
{ id: 203, name: 'Leads' },
|
|
87
|
+
],
|
|
88
|
+
}), { status: 200 });
|
|
89
|
+
}
|
|
90
|
+
return new Response('{}', { status: 404 });
|
|
91
|
+
});
|
|
92
|
+
assert.equal(out.kind, 'result');
|
|
93
|
+
assert.deepEqual(out.data, {
|
|
94
|
+
apps: [
|
|
95
|
+
{
|
|
96
|
+
id: '101',
|
|
97
|
+
name: 'Workspace',
|
|
98
|
+
entities: [
|
|
99
|
+
{ id: '201', name: 'Orders' },
|
|
100
|
+
{ id: '202', name: 'Customers' },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: '102',
|
|
105
|
+
name: 'CRM',
|
|
106
|
+
entities: [
|
|
107
|
+
{ id: '203', name: 'Leads' },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
assert.deepEqual(calls, [
|
|
113
|
+
'GET https://arcubase.example.com/apps',
|
|
114
|
+
'POST https://arcubase.example.com/apps/101/entities',
|
|
115
|
+
'POST https://arcubase.example.com/apps/102/entities',
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
63
118
|
test('admin save entity treats top-level error body as failure even on http 200', async () => {
|
|
64
119
|
const bodyFile = tempJSONFile({
|
|
65
120
|
id: 2188889845,
|
|
@@ -183,7 +238,7 @@ test('delete entity row rejects selection type outside ids', async () => {
|
|
|
183
238
|
},
|
|
184
239
|
});
|
|
185
240
|
await assert.rejects(async () => {
|
|
186
|
-
await executeCLI('user', ['
|
|
241
|
+
await executeCLI('user', ['rows', 'delete-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
187
242
|
}, (error) => {
|
|
188
243
|
assert.ok(error instanceof CLIError);
|
|
189
244
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -228,7 +283,7 @@ test('selection action accepts condition selection with selectAll', async () =>
|
|
|
228
283
|
],
|
|
229
284
|
},
|
|
230
285
|
});
|
|
231
|
-
const out = await executeCLI('user', ['
|
|
286
|
+
const out = await executeCLI('user', ['rows', '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
287
|
assert.equal(out.kind, 'result');
|
|
233
288
|
assert.equal(out.data.selection.type, 'condition');
|
|
234
289
|
assert.equal(out.data.selection.condition.selectAll, true);
|
|
@@ -244,7 +299,7 @@ test('selection action accepts condition selection with filter_* keys', async ()
|
|
|
244
299
|
},
|
|
245
300
|
},
|
|
246
301
|
});
|
|
247
|
-
const out = await executeCLI('user', ['
|
|
302
|
+
const out = await executeCLI('user', ['rows', '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
303
|
assert.equal(out.kind, 'result');
|
|
249
304
|
assert.equal(out.data.selection.type, 'condition');
|
|
250
305
|
assert.equal(out.data.selection.condition.selectAll, true);
|
|
@@ -259,7 +314,7 @@ test('selection action accepts condition selection with selectAll only for batch
|
|
|
259
314
|
},
|
|
260
315
|
},
|
|
261
316
|
});
|
|
262
|
-
const out = await executeCLI('user', ['
|
|
317
|
+
const out = await executeCLI('user', ['rows', '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
318
|
assert.equal(out.kind, 'result');
|
|
264
319
|
assert.equal(out.data.selection.type, 'condition');
|
|
265
320
|
assert.equal(out.data.selection.condition.selectAll, true);
|
|
@@ -270,7 +325,7 @@ test('selection action accepts all selection for query-style actions', async ()
|
|
|
270
325
|
type: 'all',
|
|
271
326
|
},
|
|
272
327
|
});
|
|
273
|
-
const out = await executeCLI('user', ['
|
|
328
|
+
const out = await executeCLI('user', ['rows', '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
329
|
assert.equal(out.kind, 'result');
|
|
275
330
|
assert.equal(out.data.selection.type, 'all');
|
|
276
331
|
});
|
|
@@ -286,7 +341,7 @@ test('selection action rejects condition selection for query action', async () =
|
|
|
286
341
|
},
|
|
287
342
|
});
|
|
288
343
|
await assert.rejects(async () => {
|
|
289
|
-
await executeCLI('user', ['
|
|
344
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'query', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
290
345
|
}, (error) => {
|
|
291
346
|
assert.ok(error instanceof CLIError);
|
|
292
347
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -301,7 +356,7 @@ test('selection action rejects all selection for archive action', async () => {
|
|
|
301
356
|
},
|
|
302
357
|
});
|
|
303
358
|
await assert.rejects(async () => {
|
|
304
|
-
await executeCLI('user', ['
|
|
359
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
305
360
|
}, (error) => {
|
|
306
361
|
assert.ok(error instanceof CLIError);
|
|
307
362
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -320,7 +375,7 @@ test('selection action rejects selectAll-only condition for archive action', asy
|
|
|
320
375
|
},
|
|
321
376
|
});
|
|
322
377
|
await assert.rejects(async () => {
|
|
323
|
-
await executeCLI('user', ['
|
|
378
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
324
379
|
}, (error) => {
|
|
325
380
|
assert.ok(error instanceof CLIError);
|
|
326
381
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -328,6 +383,159 @@ test('selection action rejects selectAll-only condition for archive action', asy
|
|
|
328
383
|
return true;
|
|
329
384
|
});
|
|
330
385
|
});
|
|
386
|
+
test('insert entity rejects local file path for file field and points to arcubase upload', async () => {
|
|
387
|
+
const bodyFile = tempJSONFile({
|
|
388
|
+
fields: {
|
|
389
|
+
1001: 'Lead A',
|
|
390
|
+
1007: './contract.pdf',
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
const calls = [];
|
|
394
|
+
await assert.rejects(async () => {
|
|
395
|
+
await executeCLI('user', ['rows', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
396
|
+
calls.push({ url: String(url), method: init?.method });
|
|
397
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
398
|
+
return new Response(JSON.stringify({
|
|
399
|
+
data: {
|
|
400
|
+
id: 1,
|
|
401
|
+
app_id: 1,
|
|
402
|
+
fields: [
|
|
403
|
+
{ id: 1001, type: 'text', label: 'Lead Name' },
|
|
404
|
+
{ id: 1007, type: 'file', label: 'Contract File' },
|
|
405
|
+
],
|
|
406
|
+
},
|
|
407
|
+
}), { status: 200 });
|
|
408
|
+
}
|
|
409
|
+
return new Response('{}', { status: 200 });
|
|
410
|
+
});
|
|
411
|
+
}, (error) => {
|
|
412
|
+
assert.ok(error instanceof CLIError);
|
|
413
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
414
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.fields.1007'));
|
|
415
|
+
assert.match(error.message, /arcubase upload \.\/contract\.pdf/);
|
|
416
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/uploads.md')));
|
|
417
|
+
return true;
|
|
418
|
+
});
|
|
419
|
+
assert.deepEqual(calls, [
|
|
420
|
+
{ url: 'https://arcubase.example.com/entity/app_1/entity_1', method: 'GET' },
|
|
421
|
+
]);
|
|
422
|
+
});
|
|
423
|
+
test('update row rejects object value for image field and requires upload array', async () => {
|
|
424
|
+
const bodyFile = tempJSONFile({
|
|
425
|
+
changed_fields: [1008],
|
|
426
|
+
data: {
|
|
427
|
+
1008: {
|
|
428
|
+
upload_id: 123456,
|
|
429
|
+
file: 'lead-photo.jpg',
|
|
430
|
+
file_name: 'lead-photo.jpg',
|
|
431
|
+
meta: {},
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
await assert.rejects(async () => {
|
|
436
|
+
await executeCLI('user', ['rows', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
437
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
438
|
+
return new Response(JSON.stringify({
|
|
439
|
+
data: {
|
|
440
|
+
id: 1,
|
|
441
|
+
app_id: 1,
|
|
442
|
+
fields: [
|
|
443
|
+
{ id: 1008, type: 'image', label: 'Lead Photo' },
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
}), { status: 200 });
|
|
447
|
+
}
|
|
448
|
+
return new Response('{}', { status: 200 });
|
|
449
|
+
});
|
|
450
|
+
}, (error) => {
|
|
451
|
+
assert.ok(error instanceof CLIError);
|
|
452
|
+
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
453
|
+
assert.ok((error.details?.issues ?? []).some((item) => item.path === 'body.data.1008'));
|
|
454
|
+
assert.match(error.message, /must be an array/);
|
|
455
|
+
assert.match(error.message, /arcubase upload <local-file>/);
|
|
456
|
+
return true;
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
test('insert entity accepts upload array for file field after schema preflight', async () => {
|
|
460
|
+
const bodyFile = tempJSONFile({
|
|
461
|
+
fields: {
|
|
462
|
+
1001: 'Lead A',
|
|
463
|
+
1007: [
|
|
464
|
+
{
|
|
465
|
+
upload_id: 123456,
|
|
466
|
+
file: 'contract.pdf',
|
|
467
|
+
file_name: 'contract.pdf',
|
|
468
|
+
meta: {},
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
const calls = [];
|
|
474
|
+
const out = await executeCLI('user', ['rows', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
475
|
+
calls.push({ url: String(url), method: init?.method });
|
|
476
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
477
|
+
return new Response(JSON.stringify({
|
|
478
|
+
data: {
|
|
479
|
+
id: 1,
|
|
480
|
+
app_id: 1,
|
|
481
|
+
fields: [
|
|
482
|
+
{ id: 1001, type: 'text', label: 'Lead Name' },
|
|
483
|
+
{ id: 1007, type: 'file', label: 'Contract File' },
|
|
484
|
+
],
|
|
485
|
+
},
|
|
486
|
+
}), { status: 200 });
|
|
487
|
+
}
|
|
488
|
+
return new Response(String(init?.body ?? '{}'), { status: 200 });
|
|
489
|
+
});
|
|
490
|
+
assert.equal(out.kind, 'result');
|
|
491
|
+
assert.deepEqual(calls, [
|
|
492
|
+
{ url: 'https://arcubase.example.com/entity/app_1/entity_1', method: 'GET' },
|
|
493
|
+
{ url: 'https://arcubase.example.com/entity/app_1/entity_1/insert', method: 'POST' },
|
|
494
|
+
]);
|
|
495
|
+
assert.equal(out.data.fields['1007'][0].upload_id, 123456);
|
|
496
|
+
});
|
|
497
|
+
test('insert entity decorates upload bind failures with actionable guidance', async () => {
|
|
498
|
+
const bodyFile = tempJSONFile({
|
|
499
|
+
fields: {
|
|
500
|
+
1001: 'Lead A',
|
|
501
|
+
1007: [
|
|
502
|
+
{
|
|
503
|
+
upload_id: 123456,
|
|
504
|
+
file: 'contract.pdf',
|
|
505
|
+
file_name: 'contract.pdf',
|
|
506
|
+
meta: {},
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
await assert.rejects(async () => {
|
|
512
|
+
await executeCLI('user', ['rows', 'insert-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async (url, init) => {
|
|
513
|
+
if (String(url) === 'https://arcubase.example.com/entity/app_1/entity_1' && init?.method === 'GET') {
|
|
514
|
+
return new Response(JSON.stringify({
|
|
515
|
+
data: {
|
|
516
|
+
fields: [{ id: 1007, type: 'file', label: 'Contract File' }],
|
|
517
|
+
},
|
|
518
|
+
}), { status: 200 });
|
|
519
|
+
}
|
|
520
|
+
return new Response(JSON.stringify({
|
|
521
|
+
error: {
|
|
522
|
+
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',
|
|
523
|
+
type: 'system',
|
|
524
|
+
key: 'error.system',
|
|
525
|
+
},
|
|
526
|
+
trace_id: 'req_upload_bind',
|
|
527
|
+
}), { status: 200 });
|
|
528
|
+
});
|
|
529
|
+
}, (error) => {
|
|
530
|
+
assert.ok(error instanceof CLIError);
|
|
531
|
+
assert.equal(error.code, 'UPSTREAM_BODY_ERROR');
|
|
532
|
+
assert.equal(error.details?.traceId, 'req_upload_bind');
|
|
533
|
+
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');
|
|
534
|
+
assert.match(error.details?.hint ?? '', /rerun arcubase upload/);
|
|
535
|
+
assert.ok((error.details?.docHints ?? []).some((item) => item.file.endsWith('/docs/runtime-reference/uploads.md')));
|
|
536
|
+
return true;
|
|
537
|
+
});
|
|
538
|
+
});
|
|
331
539
|
test('selection action rejects condition selection without selectAll', async () => {
|
|
332
540
|
const bodyFile = tempJSONFile({
|
|
333
541
|
selection: {
|
|
@@ -339,7 +547,7 @@ test('selection action rejects condition selection without selectAll', async ()
|
|
|
339
547
|
},
|
|
340
548
|
});
|
|
341
549
|
await assert.rejects(async () => {
|
|
342
|
-
await executeCLI('user', ['
|
|
550
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
343
551
|
}, (error) => {
|
|
344
552
|
assert.ok(error instanceof CLIError);
|
|
345
553
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -359,7 +567,7 @@ test('selection action rejects condition selection that uses text_search instead
|
|
|
359
567
|
},
|
|
360
568
|
});
|
|
361
569
|
await assert.rejects(async () => {
|
|
362
|
-
await executeCLI('user', ['
|
|
570
|
+
await executeCLI('user', ['rows', 'query-entity-selection', '--app-id', 'app_1', '--entity-id', 'entity_1', '--action', 'archive', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
363
571
|
}, (error) => {
|
|
364
572
|
assert.ok(error instanceof CLIError);
|
|
365
573
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -451,7 +659,7 @@ test('query entity rejects camelCase viewMode and points to row crud doc', async
|
|
|
451
659
|
viewMode: 'grid',
|
|
452
660
|
});
|
|
453
661
|
await assert.rejects(async () => {
|
|
454
|
-
await executeCLI('user', ['
|
|
662
|
+
await executeCLI('user', ['rows', 'query-entity', '--app-id', 'app_1', '--entity-id', 'entity_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
455
663
|
}, (error) => {
|
|
456
664
|
assert.ok(error instanceof CLIError);
|
|
457
665
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -471,7 +679,7 @@ test('update entity row rejects fields key', async () => {
|
|
|
471
679
|
changed_fields: [1002],
|
|
472
680
|
});
|
|
473
681
|
await assert.rejects(async () => {
|
|
474
|
-
await executeCLI('user', ['
|
|
682
|
+
await executeCLI('user', ['rows', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
475
683
|
}, (error) => {
|
|
476
684
|
assert.ok(error instanceof CLIError);
|
|
477
685
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
|
@@ -488,7 +696,7 @@ test('update entity row rejects changed_fields that are missing from data', asyn
|
|
|
488
696
|
changed_fields: [1003],
|
|
489
697
|
});
|
|
490
698
|
await assert.rejects(async () => {
|
|
491
|
-
await executeCLI('user', ['
|
|
699
|
+
await executeCLI('user', ['rows', 'update-entity-row', '--app-id', 'app_1', '--entity-id', 'entity_1', '--row-id', 'row_1', '--body-file', bodyFile], env, async () => new Response('{}', { status: 200 }));
|
|
492
700
|
}, (error) => {
|
|
493
701
|
assert.ok(error instanceof CLIError);
|
|
494
702
|
assert.equal(error.code, 'BODY_VALIDATION_FAILED');
|
package/dist/tests/help.test.js
CHANGED
|
@@ -24,9 +24,10 @@ test('module help prints create-entity command', async () => {
|
|
|
24
24
|
const out = await executeCLI('admin', ['app', '--help'], env, async () => new Response('{}'));
|
|
25
25
|
assert.equal(out.kind, 'help');
|
|
26
26
|
assert.match(out.text, /create-entity/);
|
|
27
|
+
assert.match(out.text, /inventory/);
|
|
27
28
|
});
|
|
28
29
|
test('module help does not require runtime env', async () => {
|
|
29
|
-
const out = await executeCLI('user', ['
|
|
30
|
+
const out = await executeCLI('user', ['rows', '--help'], undefined, async () => new Response('{}'));
|
|
30
31
|
assert.equal(out.kind, 'help');
|
|
31
32
|
assert.match(out.text, /delete-entity-row/);
|
|
32
33
|
});
|
|
@@ -37,19 +38,19 @@ test('command help prints normalized external endpoint', async () => {
|
|
|
37
38
|
assert.doesNotMatch(out.text, /endpoint: \/api\//);
|
|
38
39
|
});
|
|
39
40
|
test('command help does not require runtime env', async () => {
|
|
40
|
-
const out = await executeCLI('user', ['
|
|
41
|
+
const out = await executeCLI('user', ['rows', 'delete-entity-row', '--help'], undefined, async () => new Response('{}'));
|
|
41
42
|
assert.equal(out.kind, 'help');
|
|
42
43
|
assert.match(out.text, /endpoint: \/entity\/:app_id\/:entity_id\/delete-item/);
|
|
43
44
|
});
|
|
44
45
|
test('command help prints runtime doc hints for request body commands', async () => {
|
|
45
|
-
const out = await executeCLI('user', ['
|
|
46
|
+
const out = await executeCLI('user', ['rows', 'update-entity-row', '--help'], undefined, async () => new Response('{}'));
|
|
46
47
|
assert.equal(out.kind, 'help');
|
|
47
48
|
assert.match(out.text, /docs:/);
|
|
48
49
|
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\/
|
|
50
|
+
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/README\.md/);
|
|
50
51
|
});
|
|
51
52
|
test('command help prints query-file support', async () => {
|
|
52
|
-
const out = await executeCLI('user', ['
|
|
53
|
+
const out = await executeCLI('user', ['rows', 'query-entity', '--help'], undefined, async () => new Response('{}'));
|
|
53
54
|
assert.equal(out.kind, 'help');
|
|
54
55
|
assert.match(out.text, /query flags: --query-file/);
|
|
55
56
|
});
|
|
@@ -57,5 +58,27 @@ test('command help without request body can still point to lifecycle and example
|
|
|
57
58
|
const out = await executeCLI('admin', ['entity', 'admin-get-entity-info', '--help'], undefined, async () => new Response('{}'));
|
|
58
59
|
assert.equal(out.kind, 'help');
|
|
59
60
|
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\/
|
|
61
|
+
assert.match(out.text, /\/opt\/arcubase-sdk\/docs\/runtime-reference\/examples\/README\.md/);
|
|
62
|
+
});
|
|
63
|
+
test('app inventory help explains app discovery purpose', async () => {
|
|
64
|
+
const out = await executeCLI('admin', ['app', 'inventory', '--help'], undefined, async () => new Response('{}'));
|
|
65
|
+
assert.equal(out.kind, 'help');
|
|
66
|
+
assert.match(out.text, /list all accessible apps and each app entity list/);
|
|
67
|
+
assert.match(out.text, /use this before asking the user for app_id/);
|
|
68
|
+
});
|
|
69
|
+
test('admin-save-entity help prints row-operation next step', async () => {
|
|
70
|
+
const out = await executeCLI('admin', ['entity', 'admin-save-entity', '--help'], undefined, async () => new Response('{}'));
|
|
71
|
+
assert.equal(out.kind, 'help');
|
|
72
|
+
assert.match(out.text, /switch to arcubase for row operations/);
|
|
73
|
+
assert.match(out.text, /arcubase rows insert-entity/);
|
|
74
|
+
assert.match(out.text, /arcubase rows query-entity/);
|
|
75
|
+
});
|
|
76
|
+
test('wrong admin workflow command points to arcubase row command', async () => {
|
|
77
|
+
await assert.rejects(() => executeCLI('admin', ['workflow', 'insert-entity'], env, async () => new Response('{}')), (error) => {
|
|
78
|
+
assert.equal(error.code, 'UNKNOWN_COMMAND');
|
|
79
|
+
assert.equal(error.details?.reason, 'workflow insert-entity is not a valid command group; use arcubase rows for row operations');
|
|
80
|
+
assert.equal(error.details?.hint, 'use: arcubase rows insert-entity');
|
|
81
|
+
assert.ok((error.details?.suggestions ?? []).includes('arcubase rows insert-entity'));
|
|
82
|
+
return true;
|
|
83
|
+
});
|
|
61
84
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.test.d.ts","sourceRoot":"","sources":["../../src/tests/upload.test.ts"],"names":[],"mappings":""}
|