@uipath/data-fabric-tool 0.9.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +18263 -30988
- package/dist/tool.js +18269 -30994
- package/package.json +8 -8
- package/src/commands/entities.spec.ts +649 -8
- package/src/commands/entities.ts +272 -24
- package/src/commands/records.spec.ts +251 -10
- package/src/commands/records.ts +80 -7
|
@@ -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
|
|
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())).
|
|
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
|
|
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
|
|
1317
|
+
it("should reject empty id in updateFields", async () => {
|
|
1318
|
+
const sdk = mockSdk();
|
|
842
1319
|
vi.mocked(readJsonInput).mockResolvedValue({
|
|
843
|
-
|
|
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
|
-
'{"
|
|
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:
|
|
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
|
+
});
|