@uipath/data-fabric-tool 0.9.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.
@@ -0,0 +1,970 @@
1
+ import { Command } from "commander";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ vi.mock("../utils/sdk-client", () => ({
5
+ createDataFabricClient: vi.fn(),
6
+ }));
7
+
8
+ vi.mock("@uipath/common", async (importOriginal) => {
9
+ const actual = await importOriginal<typeof import("@uipath/common")>();
10
+ return {
11
+ ...actual,
12
+ OutputFormatter: {
13
+ success: vi.fn(),
14
+ error: vi.fn(),
15
+ },
16
+ };
17
+ });
18
+
19
+ vi.mock("../utils/input", () => ({
20
+ readJsonInput: vi.fn(),
21
+ }));
22
+
23
+ import { OutputFormatter } from "@uipath/common";
24
+ import { readJsonInput } from "../utils/input";
25
+ import { createDataFabricClient } from "../utils/sdk-client";
26
+ import { registerEntitiesCommand } from "./entities";
27
+
28
+ function buildProgram(): Command {
29
+ const program = new Command();
30
+ program.name("test").exitOverride();
31
+ registerEntitiesCommand(program);
32
+ return program;
33
+ }
34
+
35
+ function mockSdk(overrides: Record<string, unknown> = {}) {
36
+ const sdk = {
37
+ entities: {
38
+ getAll: vi.fn().mockResolvedValue([]),
39
+ getById: vi.fn().mockResolvedValue({}),
40
+ create: vi.fn().mockResolvedValue("new-entity-id"),
41
+ updateById: vi.fn().mockResolvedValue(undefined),
42
+ ...overrides,
43
+ },
44
+ };
45
+ vi.mocked(createDataFabricClient).mockResolvedValue(sdk as never);
46
+ return sdk;
47
+ }
48
+
49
+ describe("entities list", () => {
50
+ beforeEach(() => {
51
+ vi.resetAllMocks();
52
+ process.exitCode = undefined;
53
+ });
54
+
55
+ it("should register the entities command with list, get, create, and update subcommands", () => {
56
+ const program = buildProgram();
57
+ const cmd = program.commands.find((c) => c.name() === "entities");
58
+ expect(cmd).toBeDefined();
59
+ expect(cmd?.commands.map((c) => c.name())).toContain("list");
60
+ expect(cmd?.commands.map((c) => c.name())).toContain("get");
61
+ expect(cmd?.commands.map((c) => c.name())).toContain("create");
62
+ expect(cmd?.commands.map((c) => c.name())).toContain("update");
63
+ expect(cmd?.commands.map((c) => c.name())).not.toContain("delete");
64
+ });
65
+
66
+ it("should list entities successfully", async () => {
67
+ const sdk = mockSdk();
68
+ vi.mocked(sdk.entities.getAll).mockResolvedValue([
69
+ {
70
+ id: "uuid-1",
71
+ name: "invoice",
72
+ displayName: "Invoice",
73
+ entityType: "Local",
74
+ description: "Invoice entity",
75
+ fields: [{ name: "amount" }, { name: "date" }],
76
+ externalFields: [],
77
+ },
78
+ ]);
79
+
80
+ const program = buildProgram();
81
+ await program.parseAsync(["node", "test", "entities", "list"]);
82
+
83
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
84
+ expect.objectContaining({
85
+ Result: "Success",
86
+ Code: "EntityList",
87
+ Data: expect.arrayContaining([
88
+ expect.objectContaining({
89
+ Name: "invoice",
90
+ ID: "uuid-1",
91
+ FieldCount: 2,
92
+ Source: "Native",
93
+ }),
94
+ ]),
95
+ }),
96
+ );
97
+ });
98
+
99
+ it("should label federated entity with connector name", async () => {
100
+ const sdk = mockSdk();
101
+ vi.mocked(sdk.entities.getAll).mockResolvedValue([
102
+ {
103
+ id: "uuid-2",
104
+ name: "contacts",
105
+ displayName: "Contacts",
106
+ entityType: "Federated",
107
+ description: "",
108
+ fields: [],
109
+ externalFields: [
110
+ {
111
+ externalConnectionDetail: {
112
+ connectorName: "Salesforce",
113
+ },
114
+ },
115
+ ],
116
+ },
117
+ ]);
118
+
119
+ const program = buildProgram();
120
+ await program.parseAsync(["node", "test", "entities", "list"]);
121
+
122
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
123
+ expect.objectContaining({
124
+ Data: expect.arrayContaining([
125
+ expect.objectContaining({
126
+ Source: "Federated (Salesforce)",
127
+ }),
128
+ ]),
129
+ }),
130
+ );
131
+ });
132
+
133
+ it("should filter out federated entities with --native-only", async () => {
134
+ const sdk = mockSdk();
135
+ vi.mocked(sdk.entities.getAll).mockResolvedValue([
136
+ {
137
+ id: "uuid-1",
138
+ name: "invoice",
139
+ fields: [],
140
+ externalFields: [],
141
+ },
142
+ {
143
+ id: "uuid-2",
144
+ name: "contacts",
145
+ fields: [],
146
+ externalFields: [{ externalConnectionDetail: {} }],
147
+ },
148
+ ]);
149
+
150
+ const program = buildProgram();
151
+ await program.parseAsync([
152
+ "node",
153
+ "test",
154
+ "entities",
155
+ "list",
156
+ "--native-only",
157
+ ]);
158
+
159
+ const call = vi.mocked(OutputFormatter.success).mock.calls[0][0] as {
160
+ Data: { ID: string }[];
161
+ };
162
+ expect(call.Data).toHaveLength(1);
163
+ expect(call.Data[0].ID).toBe("uuid-1");
164
+ });
165
+
166
+ it("should return empty list when no entities exist", async () => {
167
+ mockSdk();
168
+
169
+ const program = buildProgram();
170
+ await program.parseAsync(["node", "test", "entities", "list"]);
171
+
172
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
173
+ expect.objectContaining({
174
+ Result: "Success",
175
+ Code: "EntityList",
176
+ Data: [],
177
+ }),
178
+ );
179
+ });
180
+
181
+ it("should error when SDK connection fails", async () => {
182
+ vi.mocked(createDataFabricClient).mockRejectedValue(
183
+ new Error("Not logged in"),
184
+ );
185
+
186
+ const program = buildProgram();
187
+ await program.parseAsync(["node", "test", "entities", "list"]);
188
+
189
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
190
+ expect.objectContaining({
191
+ Result: "Failure",
192
+ Message: "Error connecting to Data Fabric",
193
+ }),
194
+ );
195
+ expect(process.exitCode).toBe(1);
196
+ });
197
+
198
+ it("should error when list API fails", async () => {
199
+ const sdk = mockSdk();
200
+ vi.mocked(sdk.entities.getAll).mockRejectedValue(
201
+ new Error("Server error"),
202
+ );
203
+
204
+ const program = buildProgram();
205
+ await program.parseAsync(["node", "test", "entities", "list"]);
206
+
207
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
208
+ expect.objectContaining({
209
+ Result: "Failure",
210
+ Message: "Error listing entities",
211
+ }),
212
+ );
213
+ expect(process.exitCode).toBe(1);
214
+ });
215
+ });
216
+
217
+ describe("entities get", () => {
218
+ beforeEach(() => {
219
+ vi.resetAllMocks();
220
+ process.exitCode = undefined;
221
+ });
222
+
223
+ it("should return entity schema with fields", async () => {
224
+ const sdk = mockSdk();
225
+ vi.mocked(sdk.entities.getById).mockResolvedValue({
226
+ id: "uuid-1",
227
+ name: "invoice",
228
+ displayName: "Invoice",
229
+ entityType: "Local",
230
+ description: "Invoice entity",
231
+ fields: [
232
+ {
233
+ id: "field-uuid-1",
234
+ name: "amount",
235
+ displayName: "Amount",
236
+ fieldDataType: { name: "DECIMAL" },
237
+ isRequired: true,
238
+ isPrimaryKey: false,
239
+ isSystemField: false,
240
+ },
241
+ {
242
+ id: "field-uuid-2",
243
+ name: "duedate",
244
+ displayName: "Due Date",
245
+ fieldDataType: { name: "date" },
246
+ isRequired: false,
247
+ isPrimaryKey: false,
248
+ isSystemField: false,
249
+ },
250
+ ],
251
+ });
252
+
253
+ const program = buildProgram();
254
+ await program.parseAsync(["node", "test", "entities", "get", "uuid-1"]);
255
+
256
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
257
+ expect.objectContaining({
258
+ Result: "Success",
259
+ Code: "EntitySchema",
260
+ Data: expect.objectContaining({
261
+ ID: "uuid-1",
262
+ Name: "invoice",
263
+ Fields: expect.arrayContaining([
264
+ expect.objectContaining({
265
+ ID: "field-uuid-1",
266
+ Name: "amount",
267
+ Type: "DECIMAL",
268
+ Required: true,
269
+ }),
270
+ ]),
271
+ }),
272
+ }),
273
+ );
274
+ });
275
+
276
+ it("should use name as DisplayName when displayName is missing", async () => {
277
+ const sdk = mockSdk();
278
+ vi.mocked(sdk.entities.getById).mockResolvedValue({
279
+ id: "uuid-1",
280
+ name: "invoice",
281
+ fields: [],
282
+ });
283
+
284
+ const program = buildProgram();
285
+ await program.parseAsync(["node", "test", "entities", "get", "uuid-1"]);
286
+
287
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
288
+ expect.objectContaining({
289
+ Data: expect.objectContaining({ DisplayName: "invoice" }),
290
+ }),
291
+ );
292
+ });
293
+
294
+ it("should error when SDK connection fails", async () => {
295
+ vi.mocked(createDataFabricClient).mockRejectedValue(
296
+ new Error("Not logged in"),
297
+ );
298
+
299
+ const program = buildProgram();
300
+ await program.parseAsync(["node", "test", "entities", "get", "uuid-1"]);
301
+
302
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
303
+ expect.objectContaining({
304
+ Result: "Failure",
305
+ Message: "Error connecting to Data Fabric",
306
+ }),
307
+ );
308
+ expect(process.exitCode).toBe(1);
309
+ });
310
+
311
+ it("should error when entity is not found (SDK throws)", async () => {
312
+ const sdk = mockSdk();
313
+ vi.mocked(sdk.entities.getById).mockRejectedValue(
314
+ new Error("Not found"),
315
+ );
316
+
317
+ const program = buildProgram();
318
+ await program.parseAsync(["node", "test", "entities", "get", "bad-id"]);
319
+
320
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
321
+ expect.objectContaining({
322
+ Result: "Failure",
323
+ Message: "Error getting entity schema 'bad-id'",
324
+ }),
325
+ );
326
+ expect(process.exitCode).toBe(1);
327
+ });
328
+
329
+ it("should error when entity returns null (not found)", async () => {
330
+ const sdk = mockSdk();
331
+ vi.mocked(sdk.entities.getById).mockResolvedValue(null as never);
332
+
333
+ const program = buildProgram();
334
+ await program.parseAsync([
335
+ "node",
336
+ "test",
337
+ "entities",
338
+ "get",
339
+ "missing-id",
340
+ ]);
341
+
342
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
343
+ expect.objectContaining({
344
+ Result: "Failure",
345
+ Message: "Entity 'missing-id' not found",
346
+ }),
347
+ );
348
+ expect(process.exitCode).toBe(1);
349
+ });
350
+ });
351
+
352
+ describe("entities create", () => {
353
+ beforeEach(() => {
354
+ vi.resetAllMocks();
355
+ process.exitCode = undefined;
356
+ });
357
+
358
+ it("should create an entity with fields via --body", async () => {
359
+ const sdk = mockSdk();
360
+ vi.mocked(sdk.entities.create).mockResolvedValue("new-entity-id");
361
+ vi.mocked(readJsonInput).mockResolvedValue({
362
+ fields: [{ fieldName: "title", type: "STRING" }],
363
+ });
364
+
365
+ const program = buildProgram();
366
+ await program.parseAsync([
367
+ "node",
368
+ "test",
369
+ "entities",
370
+ "create",
371
+ "MyEntity",
372
+ "--body",
373
+ '{"fields":[{"fieldName":"title","type":"STRING"}]}',
374
+ ]);
375
+
376
+ expect(sdk.entities.create).toHaveBeenCalledWith(
377
+ "MyEntity",
378
+ [{ fieldName: "title", type: "STRING" }],
379
+ undefined,
380
+ );
381
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
382
+ expect.objectContaining({
383
+ Result: "Success",
384
+ Code: "EntityCreated",
385
+ Data: expect.objectContaining({ ID: "new-entity-id" }),
386
+ }),
387
+ );
388
+ });
389
+
390
+ it("should create entity with displayName, description, and isRbacEnabled", async () => {
391
+ const sdk = mockSdk();
392
+ vi.mocked(sdk.entities.create).mockResolvedValue("entity-with-opts");
393
+ vi.mocked(readJsonInput).mockResolvedValue({
394
+ fields: [{ fieldName: "amount", type: "DECIMAL" }],
395
+ displayName: "My Entity",
396
+ description: "A test entity",
397
+ isRbacEnabled: true,
398
+ });
399
+
400
+ const program = buildProgram();
401
+ await program.parseAsync([
402
+ "node",
403
+ "test",
404
+ "entities",
405
+ "create",
406
+ "MyEntity",
407
+ "--body",
408
+ '{"fields":[{"fieldName":"amount","type":"DECIMAL"}],"displayName":"My Entity","description":"A test entity","isRbacEnabled":true}',
409
+ ]);
410
+
411
+ expect(sdk.entities.create).toHaveBeenCalledWith(
412
+ "MyEntity",
413
+ [{ fieldName: "amount", type: "DECIMAL" }],
414
+ expect.objectContaining({
415
+ displayName: "My Entity",
416
+ description: "A test entity",
417
+ isRbacEnabled: true,
418
+ }),
419
+ );
420
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
421
+ expect.objectContaining({
422
+ Result: "Success",
423
+ Code: "EntityCreated",
424
+ }),
425
+ );
426
+ });
427
+
428
+ it("should create entity with only displayName (partial options)", async () => {
429
+ const sdk = mockSdk();
430
+ vi.mocked(sdk.entities.create).mockResolvedValue("entity-partial");
431
+ vi.mocked(readJsonInput).mockResolvedValue({
432
+ fields: [{ fieldName: "name", type: "STRING" }],
433
+ displayName: "Partial Entity",
434
+ });
435
+
436
+ const program = buildProgram();
437
+ await program.parseAsync([
438
+ "node",
439
+ "test",
440
+ "entities",
441
+ "create",
442
+ "PartialEntity",
443
+ "--body",
444
+ '{"fields":[{"fieldName":"name","type":"STRING"}],"displayName":"Partial Entity"}',
445
+ ]);
446
+
447
+ expect(sdk.entities.create).toHaveBeenCalledWith(
448
+ "PartialEntity",
449
+ expect.any(Array),
450
+ expect.objectContaining({ displayName: "Partial Entity" }),
451
+ );
452
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
453
+ expect.objectContaining({ Code: "EntityCreated" }),
454
+ );
455
+ });
456
+
457
+ it("should error when no --body or --file provided", async () => {
458
+ vi.mocked(readJsonInput).mockRejectedValue(
459
+ new Error("Provide entity definition via --file or --body."),
460
+ );
461
+
462
+ const program = buildProgram();
463
+ await program.parseAsync([
464
+ "node",
465
+ "test",
466
+ "entities",
467
+ "create",
468
+ "MyEntity",
469
+ ]);
470
+
471
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
472
+ expect.objectContaining({
473
+ Result: "Failure",
474
+ Message: "Error parsing entity definition",
475
+ }),
476
+ );
477
+ expect(process.exitCode).toBe(1);
478
+ });
479
+
480
+ it("should error when body is not a JSON object (array)", async () => {
481
+ vi.mocked(readJsonInput).mockResolvedValue([
482
+ { fields: [] },
483
+ ] as unknown as Record<string, unknown>);
484
+
485
+ const program = buildProgram();
486
+ await program.parseAsync([
487
+ "node",
488
+ "test",
489
+ "entities",
490
+ "create",
491
+ "MyEntity",
492
+ "--body",
493
+ '[{"fields":[]}]',
494
+ ]);
495
+
496
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
497
+ expect.objectContaining({
498
+ Result: "Failure",
499
+ Message: "Entity definition must be a JSON object",
500
+ }),
501
+ );
502
+ expect(process.exitCode).toBe(1);
503
+ });
504
+
505
+ it("should error when fields array is missing", async () => {
506
+ vi.mocked(readJsonInput).mockResolvedValue({
507
+ displayName: "No Fields Entity",
508
+ });
509
+
510
+ const program = buildProgram();
511
+ await program.parseAsync([
512
+ "node",
513
+ "test",
514
+ "entities",
515
+ "create",
516
+ "MyEntity",
517
+ "--body",
518
+ '{"displayName":"No Fields Entity"}',
519
+ ]);
520
+
521
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
522
+ expect.objectContaining({
523
+ Result: "Failure",
524
+ Message: "Entity definition must include a 'fields' array",
525
+ }),
526
+ );
527
+ expect(process.exitCode).toBe(1);
528
+ });
529
+
530
+ it("should error when a field has an invalid type", async () => {
531
+ vi.mocked(readJsonInput).mockResolvedValue({
532
+ fields: [{ fieldName: "title", type: "text" }],
533
+ });
534
+
535
+ const program = buildProgram();
536
+ await program.parseAsync([
537
+ "node",
538
+ "test",
539
+ "entities",
540
+ "create",
541
+ "MyEntity",
542
+ "--body",
543
+ '{"fields":[{"fieldName":"title","type":"text"}]}',
544
+ ]);
545
+
546
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
547
+ expect.objectContaining({
548
+ Result: "Failure",
549
+ Message: "Invalid field type in fields",
550
+ }),
551
+ );
552
+ expect(process.exitCode).toBe(1);
553
+ });
554
+
555
+ it("should error when SDK connection fails on create", async () => {
556
+ vi.mocked(readJsonInput).mockResolvedValue({
557
+ fields: [{ fieldName: "title", type: "STRING" }],
558
+ });
559
+ vi.mocked(createDataFabricClient).mockRejectedValue(
560
+ new Error("Not logged in"),
561
+ );
562
+
563
+ const program = buildProgram();
564
+ await program.parseAsync([
565
+ "node",
566
+ "test",
567
+ "entities",
568
+ "create",
569
+ "MyEntity",
570
+ "--body",
571
+ '{"fields":[{"fieldName":"title","type":"STRING"}]}',
572
+ ]);
573
+
574
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
575
+ expect.objectContaining({
576
+ Result: "Failure",
577
+ Message: "Error connecting to Data Fabric",
578
+ }),
579
+ );
580
+ expect(process.exitCode).toBe(1);
581
+ });
582
+
583
+ it("should error when create API fails", async () => {
584
+ const sdk = mockSdk();
585
+ vi.mocked(sdk.entities.create).mockRejectedValue(
586
+ new Error("Entity already exists"),
587
+ );
588
+ vi.mocked(readJsonInput).mockResolvedValue({
589
+ fields: [{ fieldName: "title", type: "STRING" }],
590
+ });
591
+
592
+ const program = buildProgram();
593
+ await program.parseAsync([
594
+ "node",
595
+ "test",
596
+ "entities",
597
+ "create",
598
+ "MyEntity",
599
+ "--body",
600
+ '{"fields":[{"fieldName":"title","type":"STRING"}]}',
601
+ ]);
602
+
603
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
604
+ expect.objectContaining({
605
+ Result: "Failure",
606
+ Message: "Error creating entity",
607
+ }),
608
+ );
609
+ expect(process.exitCode).toBe(1);
610
+ });
611
+ });
612
+
613
+ describe("entities update", () => {
614
+ beforeEach(() => {
615
+ vi.resetAllMocks();
616
+ process.exitCode = undefined;
617
+ });
618
+
619
+ it("should update entity by adding new fields (addFields)", async () => {
620
+ const sdk = mockSdk();
621
+ vi.mocked(readJsonInput).mockResolvedValue({
622
+ addFields: [{ fieldName: "newField", type: "STRING" }],
623
+ });
624
+
625
+ const program = buildProgram();
626
+ await program.parseAsync([
627
+ "node",
628
+ "test",
629
+ "entities",
630
+ "update",
631
+ "entity-id",
632
+ "--body",
633
+ '{"addFields":[{"fieldName":"newField","type":"STRING"}]}',
634
+ ]);
635
+
636
+ expect(sdk.entities.updateById).toHaveBeenCalledWith(
637
+ "entity-id",
638
+ expect.objectContaining({
639
+ addFields: [{ fieldName: "newField", type: "STRING" }],
640
+ }),
641
+ );
642
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
643
+ expect.objectContaining({
644
+ Result: "Success",
645
+ Code: "EntityUpdated",
646
+ Data: expect.objectContaining({ ID: "entity-id" }),
647
+ }),
648
+ );
649
+ });
650
+
651
+ it("should update entity by modifying existing fields (updateFields)", async () => {
652
+ const sdk = mockSdk();
653
+ vi.mocked(readJsonInput).mockResolvedValue({
654
+ updateFields: [
655
+ {
656
+ id: "field-uuid-1",
657
+ displayName: "Total Amount",
658
+ isRequired: true,
659
+ },
660
+ ],
661
+ });
662
+
663
+ const program = buildProgram();
664
+ await program.parseAsync([
665
+ "node",
666
+ "test",
667
+ "entities",
668
+ "update",
669
+ "entity-id",
670
+ "--body",
671
+ '{"updateFields":[{"id":"field-uuid-1","displayName":"Total Amount","isRequired":true}]}',
672
+ ]);
673
+
674
+ expect(sdk.entities.updateById).toHaveBeenCalledWith(
675
+ "entity-id",
676
+ expect.objectContaining({
677
+ updateFields: [
678
+ {
679
+ id: "field-uuid-1",
680
+ displayName: "Total Amount",
681
+ isRequired: true,
682
+ },
683
+ ],
684
+ }),
685
+ );
686
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
687
+ expect.objectContaining({
688
+ Result: "Success",
689
+ Code: "EntityUpdated",
690
+ }),
691
+ );
692
+ });
693
+
694
+ it("should update entity metadata only (displayName and description)", async () => {
695
+ const sdk = mockSdk();
696
+ vi.mocked(readJsonInput).mockResolvedValue({
697
+ displayName: "Updated Name",
698
+ description: "Updated description",
699
+ });
700
+
701
+ const program = buildProgram();
702
+ await program.parseAsync([
703
+ "node",
704
+ "test",
705
+ "entities",
706
+ "update",
707
+ "entity-id",
708
+ "--body",
709
+ '{"displayName":"Updated Name","description":"Updated description"}',
710
+ ]);
711
+
712
+ expect(sdk.entities.updateById).toHaveBeenCalledWith(
713
+ "entity-id",
714
+ expect.objectContaining({
715
+ displayName: "Updated Name",
716
+ description: "Updated description",
717
+ }),
718
+ );
719
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
720
+ expect.objectContaining({
721
+ Result: "Success",
722
+ Code: "EntityUpdated",
723
+ }),
724
+ );
725
+ });
726
+
727
+ it("should update entity with combined addFields, updateFields, and metadata", async () => {
728
+ const sdk = mockSdk();
729
+ vi.mocked(readJsonInput).mockResolvedValue({
730
+ addFields: [{ fieldName: "newField", type: "STRING" }],
731
+ updateFields: [{ id: "field-uuid-1", isRequired: false }],
732
+ displayName: "Combined Update",
733
+ isRbacEnabled: true,
734
+ });
735
+
736
+ const program = buildProgram();
737
+ await program.parseAsync([
738
+ "node",
739
+ "test",
740
+ "entities",
741
+ "update",
742
+ "entity-id",
743
+ "--body",
744
+ '{"addFields":[{"fieldName":"newField","type":"STRING"}],"updateFields":[{"id":"field-uuid-1","isRequired":false}],"displayName":"Combined Update","isRbacEnabled":true}',
745
+ ]);
746
+
747
+ expect(sdk.entities.updateById).toHaveBeenCalledWith(
748
+ "entity-id",
749
+ expect.objectContaining({
750
+ addFields: expect.any(Array),
751
+ updateFields: expect.any(Array),
752
+ displayName: "Combined Update",
753
+ isRbacEnabled: true,
754
+ }),
755
+ );
756
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
757
+ expect.objectContaining({
758
+ Result: "Success",
759
+ Code: "EntityUpdated",
760
+ }),
761
+ );
762
+ });
763
+
764
+ it("should reject addFields items missing fieldName", async () => {
765
+ vi.mocked(readJsonInput).mockResolvedValue({
766
+ addFields: [{ type: "STRING" }],
767
+ });
768
+
769
+ const program = buildProgram();
770
+ await program.parseAsync([
771
+ "node",
772
+ "test",
773
+ "entities",
774
+ "update",
775
+ "entity-id",
776
+ "--body",
777
+ '{"addFields":[{"type":"STRING"}]}',
778
+ ]);
779
+
780
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
781
+ expect.objectContaining({
782
+ Result: "Failure",
783
+ Message:
784
+ "Each field in addFields must include a 'fieldName' string",
785
+ }),
786
+ );
787
+ expect(process.exitCode).toBe(1);
788
+ });
789
+
790
+ it("should error when addFields has an invalid type", async () => {
791
+ vi.mocked(readJsonInput).mockResolvedValue({
792
+ addFields: [{ fieldName: "price", type: "text" }],
793
+ });
794
+
795
+ const program = buildProgram();
796
+ await program.parseAsync([
797
+ "node",
798
+ "test",
799
+ "entities",
800
+ "update",
801
+ "entity-id",
802
+ "--body",
803
+ '{"addFields":[{"fieldName":"price","type":"text"}]}',
804
+ ]);
805
+
806
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
807
+ expect.objectContaining({
808
+ Result: "Failure",
809
+ Message: "Invalid field type in addFields",
810
+ }),
811
+ );
812
+ expect(process.exitCode).toBe(1);
813
+ });
814
+
815
+ it("should reject updateFields items missing id", async () => {
816
+ vi.mocked(readJsonInput).mockResolvedValue({
817
+ updateFields: [{ displayName: "Total Amount" }],
818
+ });
819
+
820
+ const program = buildProgram();
821
+ await program.parseAsync([
822
+ "node",
823
+ "test",
824
+ "entities",
825
+ "update",
826
+ "entity-id",
827
+ "--body",
828
+ '{"updateFields":[{"displayName":"Total Amount"}]}',
829
+ ]);
830
+
831
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
832
+ expect.objectContaining({
833
+ Result: "Failure",
834
+ Message:
835
+ "Each field in updateFields must include an 'id' string",
836
+ }),
837
+ );
838
+ expect(process.exitCode).toBe(1);
839
+ });
840
+
841
+ it("should reject removeFields with explicit error", async () => {
842
+ vi.mocked(readJsonInput).mockResolvedValue({
843
+ removeFields: ["oldField"],
844
+ });
845
+
846
+ const program = buildProgram();
847
+ await program.parseAsync([
848
+ "node",
849
+ "test",
850
+ "entities",
851
+ "update",
852
+ "entity-id",
853
+ "--body",
854
+ '{"removeFields":["oldField"]}',
855
+ ]);
856
+
857
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
858
+ expect.objectContaining({
859
+ Result: "Failure",
860
+ Message: "removeFields is not supported",
861
+ }),
862
+ );
863
+ expect(process.exitCode).toBe(1);
864
+ });
865
+
866
+ it("should error when no --body or --file provided", async () => {
867
+ vi.mocked(readJsonInput).mockRejectedValue(
868
+ new Error("Provide update options via --file or --body."),
869
+ );
870
+
871
+ const program = buildProgram();
872
+ await program.parseAsync([
873
+ "node",
874
+ "test",
875
+ "entities",
876
+ "update",
877
+ "entity-id",
878
+ ]);
879
+
880
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
881
+ expect.objectContaining({
882
+ Result: "Failure",
883
+ Message: "Error parsing update options",
884
+ }),
885
+ );
886
+ expect(process.exitCode).toBe(1);
887
+ });
888
+
889
+ it("should error when update options body is not a JSON object", async () => {
890
+ vi.mocked(readJsonInput).mockResolvedValue([
891
+ "addFields",
892
+ ] as unknown as Record<string, unknown>);
893
+
894
+ const program = buildProgram();
895
+ await program.parseAsync([
896
+ "node",
897
+ "test",
898
+ "entities",
899
+ "update",
900
+ "entity-id",
901
+ "--body",
902
+ '["addFields"]',
903
+ ]);
904
+
905
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
906
+ expect.objectContaining({
907
+ Result: "Failure",
908
+ Message: "Update options must be a JSON object",
909
+ }),
910
+ );
911
+ expect(process.exitCode).toBe(1);
912
+ });
913
+
914
+ it("should error when SDK connection fails on update", async () => {
915
+ vi.mocked(readJsonInput).mockResolvedValue({
916
+ displayName: "New Name",
917
+ });
918
+ vi.mocked(createDataFabricClient).mockRejectedValue(
919
+ new Error("Not logged in"),
920
+ );
921
+
922
+ const program = buildProgram();
923
+ await program.parseAsync([
924
+ "node",
925
+ "test",
926
+ "entities",
927
+ "update",
928
+ "entity-id",
929
+ "--body",
930
+ '{"displayName":"New Name"}',
931
+ ]);
932
+
933
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
934
+ expect.objectContaining({
935
+ Result: "Failure",
936
+ Message: "Error connecting to Data Fabric",
937
+ }),
938
+ );
939
+ expect(process.exitCode).toBe(1);
940
+ });
941
+
942
+ it("should error when updateById API fails", async () => {
943
+ const sdk = mockSdk();
944
+ vi.mocked(sdk.entities.updateById).mockRejectedValue(
945
+ new Error("Update failed"),
946
+ );
947
+ vi.mocked(readJsonInput).mockResolvedValue({
948
+ addFields: [{ fieldName: "newField", type: "STRING" }],
949
+ });
950
+
951
+ const program = buildProgram();
952
+ await program.parseAsync([
953
+ "node",
954
+ "test",
955
+ "entities",
956
+ "update",
957
+ "entity-id",
958
+ "--body",
959
+ '{"addFields":[{"fieldName":"newField","type":"STRING"}]}',
960
+ ]);
961
+
962
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
963
+ expect.objectContaining({
964
+ Result: "Failure",
965
+ Message: "Error updating entity",
966
+ }),
967
+ );
968
+ expect(process.exitCode).toBe(1);
969
+ });
970
+ });