@uipath/data-fabric-tool 0.9.1 → 1.0.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.
@@ -39,6 +39,7 @@ function mockSdk(overrides: Record<string, unknown> = {}) {
39
39
  getById: vi.fn().mockResolvedValue({}),
40
40
  create: vi.fn().mockResolvedValue("new-entity-id"),
41
41
  updateById: vi.fn().mockResolvedValue(undefined),
42
+ deleteById: vi.fn().mockResolvedValue(undefined),
42
43
  ...overrides,
43
44
  },
44
45
  };
@@ -52,7 +53,7 @@ describe("entities list", () => {
52
53
  process.exitCode = undefined;
53
54
  });
54
55
 
55
- it("should register the entities command with list, get, create, and update subcommands", () => {
56
+ it("should register the entities command with list, get, create, update, and delete subcommands", () => {
56
57
  const program = buildProgram();
57
58
  const cmd = program.commands.find((c) => c.name() === "entities");
58
59
  expect(cmd).toBeDefined();
@@ -60,7 +61,17 @@ describe("entities list", () => {
60
61
  expect(cmd?.commands.map((c) => c.name())).toContain("get");
61
62
  expect(cmd?.commands.map((c) => c.name())).toContain("create");
62
63
  expect(cmd?.commands.map((c) => c.name())).toContain("update");
63
- expect(cmd?.commands.map((c) => c.name())).not.toContain("delete");
64
+ expect(cmd?.commands.map((c) => c.name())).toContain("delete");
65
+ });
66
+
67
+ it("should expose the delete subcommand in --help (not hidden)", () => {
68
+ const program = buildProgram();
69
+ const cmd = program.commands.find((c) => c.name() === "entities");
70
+ const deleteCmd = cmd?.commands.find((c) => c.name() === "delete");
71
+ expect(deleteCmd).toBeDefined();
72
+ expect(
73
+ (deleteCmd as unknown as { _hidden?: boolean })._hidden,
74
+ ).toBeFalsy();
64
75
  });
65
76
 
66
77
  it("should list entities successfully", async () => {
@@ -527,6 +538,31 @@ describe("entities create", () => {
527
538
  expect(process.exitCode).toBe(1);
528
539
  });
529
540
 
541
+ it("should error when a field is missing fieldName", async () => {
542
+ vi.mocked(readJsonInput).mockResolvedValue({
543
+ fields: [{ type: "STRING" }],
544
+ });
545
+
546
+ const program = buildProgram();
547
+ await program.parseAsync([
548
+ "node",
549
+ "test",
550
+ "entities",
551
+ "create",
552
+ "MyEntity",
553
+ "--body",
554
+ '{"fields":[{"type":"STRING"}]}',
555
+ ]);
556
+
557
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
558
+ expect.objectContaining({
559
+ Result: "Failure",
560
+ Message: "Each field must include a 'fieldName' string",
561
+ }),
562
+ );
563
+ expect(process.exitCode).toBe(1);
564
+ });
565
+
530
566
  it("should error when a field has an invalid type", async () => {
531
567
  vi.mocked(readJsonInput).mockResolvedValue({
532
568
  fields: [{ fieldName: "title", type: "text" }],
@@ -781,7 +817,7 @@ describe("entities update", () => {
781
817
  expect.objectContaining({
782
818
  Result: "Failure",
783
819
  Message:
784
- "Each field in addFields must include a 'fieldName' string",
820
+ "Each field in addFields must include a non-empty 'fieldName' string",
785
821
  }),
786
822
  );
787
823
  expect(process.exitCode).toBe(1);
@@ -832,15 +868,456 @@ describe("entities update", () => {
832
868
  expect.objectContaining({
833
869
  Result: "Failure",
834
870
  Message:
835
- "Each field in updateFields must include an 'id' string",
871
+ "Each field in updateFields must include a non-empty 'id' string",
872
+ }),
873
+ );
874
+ expect(process.exitCode).toBe(1);
875
+ });
876
+
877
+ it("should reject removeFields when not an array", async () => {
878
+ vi.mocked(readJsonInput).mockResolvedValue({
879
+ removeFields: "oldField",
880
+ });
881
+
882
+ const program = buildProgram();
883
+ await program.parseAsync([
884
+ "node",
885
+ "test",
886
+ "entities",
887
+ "update",
888
+ "entity-id",
889
+ "--body",
890
+ '{"removeFields":"oldField"}',
891
+ "--confirm",
892
+ "--reason",
893
+ "drop legacy",
894
+ ]);
895
+
896
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
897
+ expect.objectContaining({
898
+ Result: "Failure",
899
+ Message: "'removeFields' must be an array",
900
+ }),
901
+ );
902
+ expect(process.exitCode).toBe(1);
903
+ });
904
+
905
+ it("should reject removeFields entries missing fieldName", async () => {
906
+ vi.mocked(readJsonInput).mockResolvedValue({
907
+ removeFields: [{}],
908
+ });
909
+
910
+ const program = buildProgram();
911
+ await program.parseAsync([
912
+ "node",
913
+ "test",
914
+ "entities",
915
+ "update",
916
+ "entity-id",
917
+ "--body",
918
+ '{"removeFields":[{}]}',
919
+ "--confirm",
920
+ "--reason",
921
+ "drop legacy",
922
+ ]);
923
+
924
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
925
+ expect.objectContaining({
926
+ Result: "Failure",
927
+ Message:
928
+ "Each field in removeFields must include a non-empty 'fieldName' string",
929
+ }),
930
+ );
931
+ expect(process.exitCode).toBe(1);
932
+ });
933
+
934
+ it("should reject removeFields without --confirm", async () => {
935
+ const sdk = mockSdk();
936
+ vi.mocked(readJsonInput).mockResolvedValue({
937
+ removeFields: [{ fieldName: "oldField" }],
938
+ });
939
+
940
+ const program = buildProgram();
941
+ await program.parseAsync([
942
+ "node",
943
+ "test",
944
+ "entities",
945
+ "update",
946
+ "entity-id",
947
+ "--body",
948
+ '{"removeFields":[{"fieldName":"oldField"}]}',
949
+ "--reason",
950
+ "drop legacy",
951
+ ]);
952
+
953
+ expect(sdk.entities.updateById).not.toHaveBeenCalled();
954
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
955
+ expect.objectContaining({
956
+ Result: "Failure",
957
+ Message: "Confirmation required for destructive operation",
958
+ }),
959
+ );
960
+ expect(process.exitCode).toBe(1);
961
+ });
962
+
963
+ it("should reject removeFields without --reason", async () => {
964
+ const sdk = mockSdk();
965
+ vi.mocked(readJsonInput).mockResolvedValue({
966
+ removeFields: [{ fieldName: "oldField" }],
967
+ });
968
+
969
+ const program = buildProgram();
970
+ await program.parseAsync([
971
+ "node",
972
+ "test",
973
+ "entities",
974
+ "update",
975
+ "entity-id",
976
+ "--body",
977
+ '{"removeFields":[{"fieldName":"oldField"}]}',
978
+ "--confirm",
979
+ ]);
980
+
981
+ expect(sdk.entities.updateById).not.toHaveBeenCalled();
982
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
983
+ expect.objectContaining({
984
+ Result: "Failure",
985
+ Message: "Reason required for destructive operation",
986
+ }),
987
+ );
988
+ expect(process.exitCode).toBe(1);
989
+ });
990
+
991
+ it("should remove fields when --confirm and --reason are present", async () => {
992
+ const sdk = mockSdk();
993
+ vi.mocked(readJsonInput).mockResolvedValue({
994
+ removeFields: [
995
+ { fieldName: "oldField" },
996
+ { fieldName: "anotherOld" },
997
+ ],
998
+ });
999
+
1000
+ const program = buildProgram();
1001
+ await program.parseAsync([
1002
+ "node",
1003
+ "test",
1004
+ "entities",
1005
+ "update",
1006
+ "entity-id",
1007
+ "--body",
1008
+ '{"removeFields":[{"fieldName":"oldField"},{"fieldName":"anotherOld"}]}',
1009
+ "--confirm",
1010
+ "--reason",
1011
+ "drop legacy fields",
1012
+ ]);
1013
+
1014
+ expect(sdk.entities.updateById).toHaveBeenCalledWith(
1015
+ "entity-id",
1016
+ expect.objectContaining({
1017
+ removeFields: [
1018
+ { fieldName: "oldField" },
1019
+ { fieldName: "anotherOld" },
1020
+ ],
1021
+ }),
1022
+ );
1023
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
1024
+ expect.objectContaining({
1025
+ Result: "Success",
1026
+ Code: "EntityUpdated",
1027
+ Data: expect.objectContaining({
1028
+ ID: "entity-id",
1029
+ RemovedFields: ["oldField", "anotherOld"],
1030
+ Reason: "drop legacy fields",
1031
+ }),
1032
+ }),
1033
+ );
1034
+ });
1035
+
1036
+ it("should not include RemovedFields/Reason in payload when no removeFields", async () => {
1037
+ mockSdk();
1038
+ vi.mocked(readJsonInput).mockResolvedValue({
1039
+ displayName: "Just a Rename",
1040
+ });
1041
+
1042
+ const program = buildProgram();
1043
+ await program.parseAsync([
1044
+ "node",
1045
+ "test",
1046
+ "entities",
1047
+ "update",
1048
+ "entity-id",
1049
+ "--body",
1050
+ '{"displayName":"Just a Rename"}',
1051
+ ]);
1052
+
1053
+ const call = vi.mocked(OutputFormatter.success).mock.calls[0][0] as {
1054
+ Data: Record<string, unknown>;
1055
+ };
1056
+ expect(call.Data).toEqual({ ID: "entity-id" });
1057
+ });
1058
+
1059
+ it("should accept addFields + removeFields + updateFields + metadata in one call", async () => {
1060
+ const sdk = mockSdk();
1061
+ vi.mocked(readJsonInput).mockResolvedValue({
1062
+ addFields: [{ fieldName: "newField", type: "STRING" }],
1063
+ removeFields: [{ fieldName: "oldField" }],
1064
+ updateFields: [
1065
+ {
1066
+ id: "field-uuid-1",
1067
+ displayName: "Renamed",
1068
+ isRequired: true,
1069
+ },
1070
+ ],
1071
+ displayName: "Combined",
1072
+ description: "all-in-one update",
1073
+ isRbacEnabled: true,
1074
+ });
1075
+
1076
+ const program = buildProgram();
1077
+ await program.parseAsync([
1078
+ "node",
1079
+ "test",
1080
+ "entities",
1081
+ "update",
1082
+ "entity-id",
1083
+ "--body",
1084
+ '{"addFields":[{"fieldName":"newField","type":"STRING"}],"removeFields":[{"fieldName":"oldField"}],"updateFields":[{"id":"field-uuid-1","displayName":"Renamed","isRequired":true}],"displayName":"Combined","description":"all-in-one update","isRbacEnabled":true}',
1085
+ "--confirm",
1086
+ "--reason",
1087
+ "schema overhaul",
1088
+ ]);
1089
+
1090
+ expect(sdk.entities.updateById).toHaveBeenCalledWith(
1091
+ "entity-id",
1092
+ expect.objectContaining({
1093
+ addFields: [{ fieldName: "newField", type: "STRING" }],
1094
+ removeFields: [{ fieldName: "oldField" }],
1095
+ updateFields: [
1096
+ {
1097
+ id: "field-uuid-1",
1098
+ displayName: "Renamed",
1099
+ isRequired: true,
1100
+ },
1101
+ ],
1102
+ displayName: "Combined",
1103
+ description: "all-in-one update",
1104
+ isRbacEnabled: true,
1105
+ }),
1106
+ );
1107
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
1108
+ expect.objectContaining({
1109
+ Result: "Success",
1110
+ Code: "EntityUpdated",
1111
+ Data: expect.objectContaining({
1112
+ ID: "entity-id",
1113
+ RemovedFields: ["oldField"],
1114
+ Reason: "schema overhaul",
1115
+ }),
1116
+ }),
1117
+ );
1118
+ });
1119
+
1120
+ it("should reject when same fieldName appears in addFields and removeFields", async () => {
1121
+ const sdk = mockSdk();
1122
+ vi.mocked(readJsonInput).mockResolvedValue({
1123
+ addFields: [{ fieldName: "shared", type: "STRING" }],
1124
+ removeFields: [{ fieldName: "shared" }],
1125
+ });
1126
+
1127
+ const program = buildProgram();
1128
+ await program.parseAsync([
1129
+ "node",
1130
+ "test",
1131
+ "entities",
1132
+ "update",
1133
+ "entity-id",
1134
+ "--body",
1135
+ '{"addFields":[{"fieldName":"shared","type":"STRING"}],"removeFields":[{"fieldName":"shared"}]}',
1136
+ "--confirm",
1137
+ "--reason",
1138
+ "recreate",
1139
+ ]);
1140
+
1141
+ expect(sdk.entities.updateById).not.toHaveBeenCalled();
1142
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1143
+ expect.objectContaining({
1144
+ Result: "Failure",
1145
+ Message:
1146
+ "Field 'shared' appears in both addFields and removeFields",
1147
+ }),
1148
+ );
1149
+ expect(process.exitCode).toBe(1);
1150
+ });
1151
+
1152
+ it("should reject duplicate fieldNames within addFields", async () => {
1153
+ const sdk = mockSdk();
1154
+ vi.mocked(readJsonInput).mockResolvedValue({
1155
+ addFields: [
1156
+ { fieldName: "dup", type: "STRING" },
1157
+ { fieldName: "dup", type: "INTEGER" },
1158
+ ],
1159
+ });
1160
+
1161
+ const program = buildProgram();
1162
+ await program.parseAsync([
1163
+ "node",
1164
+ "test",
1165
+ "entities",
1166
+ "update",
1167
+ "entity-id",
1168
+ "--body",
1169
+ '{"addFields":[{"fieldName":"dup","type":"STRING"},{"fieldName":"dup","type":"INTEGER"}]}',
1170
+ ]);
1171
+
1172
+ expect(sdk.entities.updateById).not.toHaveBeenCalled();
1173
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1174
+ expect.objectContaining({
1175
+ Result: "Failure",
1176
+ Message: "Duplicate fieldName 'dup' in addFields",
1177
+ }),
1178
+ );
1179
+ expect(process.exitCode).toBe(1);
1180
+ });
1181
+
1182
+ it("should reject addFields when not an array", async () => {
1183
+ const sdk = mockSdk();
1184
+ vi.mocked(readJsonInput).mockResolvedValue({
1185
+ addFields: "newField",
1186
+ });
1187
+
1188
+ const program = buildProgram();
1189
+ await program.parseAsync([
1190
+ "node",
1191
+ "test",
1192
+ "entities",
1193
+ "update",
1194
+ "entity-id",
1195
+ "--body",
1196
+ '{"addFields":"newField"}',
1197
+ ]);
1198
+
1199
+ expect(sdk.entities.updateById).not.toHaveBeenCalled();
1200
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1201
+ expect.objectContaining({
1202
+ Result: "Failure",
1203
+ Message: "'addFields' must be an array",
1204
+ }),
1205
+ );
1206
+ expect(process.exitCode).toBe(1);
1207
+ });
1208
+
1209
+ it("should reject updateFields when not an array", async () => {
1210
+ const sdk = mockSdk();
1211
+ vi.mocked(readJsonInput).mockResolvedValue({
1212
+ updateFields: { id: "x", displayName: "y" },
1213
+ });
1214
+
1215
+ const program = buildProgram();
1216
+ await program.parseAsync([
1217
+ "node",
1218
+ "test",
1219
+ "entities",
1220
+ "update",
1221
+ "entity-id",
1222
+ "--body",
1223
+ '{"updateFields":{"id":"x","displayName":"y"}}',
1224
+ ]);
1225
+
1226
+ expect(sdk.entities.updateById).not.toHaveBeenCalled();
1227
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1228
+ expect.objectContaining({
1229
+ Result: "Failure",
1230
+ Message: "'updateFields' must be an array",
1231
+ }),
1232
+ );
1233
+ expect(process.exitCode).toBe(1);
1234
+ });
1235
+
1236
+ it("should drop unknown keys before forwarding to the SDK", async () => {
1237
+ const sdk = mockSdk();
1238
+ vi.mocked(readJsonInput).mockResolvedValue({
1239
+ displayName: "Renamed",
1240
+ unknownGarbage: "should be dropped",
1241
+ displayname: "typo, lowercase d",
1242
+ __proto__: { malicious: true },
1243
+ });
1244
+
1245
+ const program = buildProgram();
1246
+ await program.parseAsync([
1247
+ "node",
1248
+ "test",
1249
+ "entities",
1250
+ "update",
1251
+ "entity-id",
1252
+ "--body",
1253
+ '{"displayName":"Renamed","unknownGarbage":"x","displayname":"y"}',
1254
+ ]);
1255
+
1256
+ expect(sdk.entities.updateById).toHaveBeenCalledTimes(1);
1257
+ const [, payload] = vi.mocked(sdk.entities.updateById).mock
1258
+ .calls[0] as [string, Record<string, unknown>];
1259
+ expect(payload).toEqual({ displayName: "Renamed" });
1260
+ expect(payload).not.toHaveProperty("unknownGarbage");
1261
+ expect(payload).not.toHaveProperty("displayname");
1262
+ });
1263
+
1264
+ it("should treat empty removeFields as no-op (no --confirm/--reason needed)", async () => {
1265
+ const sdk = mockSdk();
1266
+ vi.mocked(readJsonInput).mockResolvedValue({
1267
+ removeFields: [],
1268
+ displayName: "Just rename",
1269
+ });
1270
+
1271
+ const program = buildProgram();
1272
+ await program.parseAsync([
1273
+ "node",
1274
+ "test",
1275
+ "entities",
1276
+ "update",
1277
+ "entity-id",
1278
+ "--body",
1279
+ '{"removeFields":[],"displayName":"Just rename"}',
1280
+ ]);
1281
+
1282
+ expect(sdk.entities.updateById).toHaveBeenCalled();
1283
+ const call = vi.mocked(OutputFormatter.success).mock.calls[0][0] as {
1284
+ Data: Record<string, unknown>;
1285
+ };
1286
+ expect(call.Data).toEqual({ ID: "entity-id" });
1287
+ });
1288
+
1289
+ it("should reject empty fieldName in addFields", async () => {
1290
+ const sdk = mockSdk();
1291
+ vi.mocked(readJsonInput).mockResolvedValue({
1292
+ addFields: [{ fieldName: " ", type: "STRING" }],
1293
+ });
1294
+
1295
+ const program = buildProgram();
1296
+ await program.parseAsync([
1297
+ "node",
1298
+ "test",
1299
+ "entities",
1300
+ "update",
1301
+ "entity-id",
1302
+ "--body",
1303
+ '{"addFields":[{"fieldName":" ","type":"STRING"}]}',
1304
+ ]);
1305
+
1306
+ expect(sdk.entities.updateById).not.toHaveBeenCalled();
1307
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1308
+ expect.objectContaining({
1309
+ Result: "Failure",
1310
+ Message:
1311
+ "Each field in addFields must include a non-empty 'fieldName' string",
836
1312
  }),
837
1313
  );
838
1314
  expect(process.exitCode).toBe(1);
839
1315
  });
840
1316
 
841
- it("should reject removeFields with explicit error", async () => {
1317
+ it("should reject empty id in updateFields", async () => {
1318
+ const sdk = mockSdk();
842
1319
  vi.mocked(readJsonInput).mockResolvedValue({
843
- removeFields: ["oldField"],
1320
+ updateFields: [{ id: "", displayName: "x" }],
844
1321
  });
845
1322
 
846
1323
  const program = buildProgram();
@@ -851,13 +1328,15 @@ describe("entities update", () => {
851
1328
  "update",
852
1329
  "entity-id",
853
1330
  "--body",
854
- '{"removeFields":["oldField"]}',
1331
+ '{"updateFields":[{"id":"","displayName":"x"}]}',
855
1332
  ]);
856
1333
 
1334
+ expect(sdk.entities.updateById).not.toHaveBeenCalled();
857
1335
  expect(OutputFormatter.error).toHaveBeenCalledWith(
858
1336
  expect.objectContaining({
859
1337
  Result: "Failure",
860
- Message: "removeFields is not supported",
1338
+ Message:
1339
+ "Each field in updateFields must include a non-empty 'id' string",
861
1340
  }),
862
1341
  );
863
1342
  expect(process.exitCode).toBe(1);
@@ -968,3 +1447,165 @@ describe("entities update", () => {
968
1447
  expect(process.exitCode).toBe(1);
969
1448
  });
970
1449
  });
1450
+
1451
+ describe("entities delete", () => {
1452
+ beforeEach(() => {
1453
+ vi.resetAllMocks();
1454
+ process.exitCode = undefined;
1455
+ });
1456
+
1457
+ it("should delete an entity successfully with --confirm and --reason", async () => {
1458
+ const sdk = mockSdk();
1459
+
1460
+ const program = buildProgram();
1461
+ await program.parseAsync([
1462
+ "node",
1463
+ "test",
1464
+ "entities",
1465
+ "delete",
1466
+ "entity-to-delete",
1467
+ "--confirm",
1468
+ "--reason",
1469
+ "cleanup after eval",
1470
+ ]);
1471
+
1472
+ expect(sdk.entities.deleteById).toHaveBeenCalledWith(
1473
+ "entity-to-delete",
1474
+ );
1475
+ expect(OutputFormatter.success).toHaveBeenCalledWith(
1476
+ expect.objectContaining({
1477
+ Result: "Success",
1478
+ Code: "EntityDeleted",
1479
+ Data: expect.objectContaining({
1480
+ ID: "entity-to-delete",
1481
+ Reason: "cleanup after eval",
1482
+ }),
1483
+ }),
1484
+ );
1485
+ });
1486
+
1487
+ it("should error when --confirm is missing", async () => {
1488
+ const sdk = mockSdk();
1489
+
1490
+ const program = buildProgram();
1491
+ await program.parseAsync([
1492
+ "node",
1493
+ "test",
1494
+ "entities",
1495
+ "delete",
1496
+ "entity-id",
1497
+ "--reason",
1498
+ "cleanup",
1499
+ ]);
1500
+
1501
+ expect(sdk.entities.deleteById).not.toHaveBeenCalled();
1502
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1503
+ expect.objectContaining({
1504
+ Result: "Failure",
1505
+ Message: "Confirmation required for destructive operation",
1506
+ }),
1507
+ );
1508
+ expect(process.exitCode).toBe(1);
1509
+ });
1510
+
1511
+ it("should error when --reason is missing", async () => {
1512
+ const sdk = mockSdk();
1513
+
1514
+ const program = buildProgram();
1515
+ await program.parseAsync([
1516
+ "node",
1517
+ "test",
1518
+ "entities",
1519
+ "delete",
1520
+ "entity-id",
1521
+ "--confirm",
1522
+ ]);
1523
+
1524
+ expect(sdk.entities.deleteById).not.toHaveBeenCalled();
1525
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1526
+ expect.objectContaining({
1527
+ Result: "Failure",
1528
+ Message: "Reason required for destructive operation",
1529
+ }),
1530
+ );
1531
+ expect(process.exitCode).toBe(1);
1532
+ });
1533
+
1534
+ it("should error when --reason is empty/whitespace", async () => {
1535
+ const sdk = mockSdk();
1536
+
1537
+ const program = buildProgram();
1538
+ await program.parseAsync([
1539
+ "node",
1540
+ "test",
1541
+ "entities",
1542
+ "delete",
1543
+ "entity-id",
1544
+ "--confirm",
1545
+ "--reason",
1546
+ " ",
1547
+ ]);
1548
+
1549
+ expect(sdk.entities.deleteById).not.toHaveBeenCalled();
1550
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1551
+ expect.objectContaining({
1552
+ Result: "Failure",
1553
+ Message: "Reason required for destructive operation",
1554
+ }),
1555
+ );
1556
+ expect(process.exitCode).toBe(1);
1557
+ });
1558
+
1559
+ it("should error when SDK connection fails", async () => {
1560
+ vi.mocked(createDataFabricClient).mockRejectedValue(
1561
+ new Error("Not logged in"),
1562
+ );
1563
+
1564
+ const program = buildProgram();
1565
+ await program.parseAsync([
1566
+ "node",
1567
+ "test",
1568
+ "entities",
1569
+ "delete",
1570
+ "entity-id",
1571
+ "--confirm",
1572
+ "--reason",
1573
+ "cleanup",
1574
+ ]);
1575
+
1576
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1577
+ expect.objectContaining({
1578
+ Result: "Failure",
1579
+ Message: "Error connecting to Data Fabric",
1580
+ }),
1581
+ );
1582
+ expect(process.exitCode).toBe(1);
1583
+ });
1584
+
1585
+ it("should error when delete API fails", async () => {
1586
+ const sdk = mockSdk();
1587
+ vi.mocked(sdk.entities.deleteById).mockRejectedValue(
1588
+ new Error("Entity not found"),
1589
+ );
1590
+
1591
+ const program = buildProgram();
1592
+ await program.parseAsync([
1593
+ "node",
1594
+ "test",
1595
+ "entities",
1596
+ "delete",
1597
+ "bad-entity-id",
1598
+ "--confirm",
1599
+ "--reason",
1600
+ "cleanup",
1601
+ ]);
1602
+
1603
+ expect(OutputFormatter.error).toHaveBeenCalledWith(
1604
+ expect.objectContaining({
1605
+ Result: "Failure",
1606
+ Message: "Error deleting entity 'bad-entity-id'",
1607
+ }),
1608
+ );
1609
+ expect(process.exitCode).toBe(1);
1610
+ });
1611
+ });