@uipath/tasks-tool 1.201.0-preview.115 → 1.201.0-preview.121
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/browser.json +2 -1
- package/dist/folder-picker-interactive-2kj361mt.js +1850 -0
- package/dist/folder-resolver-htdmzdhy.js +92 -0
- package/dist/index.js +5 -3
- package/dist/tool-0n9mj887.js +2448 -0
- package/dist/tool-rsk1xt86.js +6026 -0
- package/dist/{tool-vw6gha5d.js → tool-ww671age.js} +7469 -10469
- package/dist/tool.js +3 -2
- package/package.json +2 -2
- package/src/commands/_shared/catalog-options.spec.ts +114 -0
- package/src/commands/_shared/catalog-options.ts +122 -0
- package/src/commands/_shared/connect.spec.ts +52 -0
- package/src/commands/_shared/connect.ts +31 -0
- package/src/commands/_shared/constants.ts +1 -0
- package/src/commands/_shared/folder-picker-interactive.spec.ts +92 -0
- package/src/commands/_shared/folder-picker-interactive.ts +76 -0
- package/src/commands/_shared/folder-picker.spec.ts +159 -0
- package/src/commands/_shared/folder-picker.ts +113 -0
- package/src/commands/_shared/folder-resolver.spec.ts +152 -0
- package/src/commands/_shared/folder-resolver.ts +113 -0
- package/src/commands/_shared/parse.spec.ts +69 -0
- package/src/commands/_shared/parse.ts +43 -0
- package/src/commands/catalogs.ts +277 -0
- package/src/commands/comments.ts +160 -0
- package/src/commands/data.ts +137 -0
- package/src/commands/labels.ts +93 -0
- package/src/commands/metadata.ts +139 -0
- package/src/commands/tasks.spec.ts +1557 -2
- package/src/commands/tasks.ts +191 -280
- package/src/tool.ts +10 -0
- package/src/utils/sdk-client.ts +5 -0
- package/tests/assignable-users.e2e.test.ts +58 -4
- package/tests/catalogs.e2e.test.ts +393 -0
- package/tests/comments.e2e.test.ts +223 -0
- package/tests/complete.e2e.test.ts +9 -9
- package/tests/data.e2e.test.ts +187 -0
- package/tests/get.e2e.test.ts +4 -2
- package/tests/labels.e2e.test.ts +184 -0
- package/tests/list.e2e.test.ts +110 -0
- package/tests/metadata.e2e.test.ts +186 -0
|
@@ -91,26 +91,26 @@ describe("tasks complete e2e", () => {
|
|
|
91
91
|
EXEC_TIMEOUT,
|
|
92
92
|
);
|
|
93
93
|
|
|
94
|
+
// ── Tenant read (requires login) ─────────────────────────────────────────
|
|
95
|
+
|
|
94
96
|
it(
|
|
95
|
-
"S2: missing
|
|
97
|
+
"S2: missing folder fails loud when not interactive",
|
|
96
98
|
() => {
|
|
97
99
|
const raw = localCliRaw(
|
|
98
100
|
tempDir,
|
|
99
101
|
"tasks complete 5 --type FormTask --output json",
|
|
100
102
|
);
|
|
101
|
-
expect(raw.exitCode).toBe(
|
|
103
|
+
expect(raw.exitCode).not.toBe(0);
|
|
102
104
|
const res = parseOutput(raw.stdout, "json") as CompleteEnvelope;
|
|
103
|
-
expect(res.Result).toBe(
|
|
104
|
-
expect(res.
|
|
105
|
-
expect(res.
|
|
106
|
-
|
|
107
|
-
);
|
|
105
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
106
|
+
expect(res.Message).toBe("A folder is required");
|
|
107
|
+
expect(res.Instructions).toMatch(/--folder-id/);
|
|
108
|
+
expect(res.Instructions).toMatch(/--folder-path/);
|
|
109
|
+
expect(res.Instructions).toMatch(/--folder-key/);
|
|
108
110
|
},
|
|
109
111
|
EXEC_TIMEOUT,
|
|
110
112
|
);
|
|
111
113
|
|
|
112
|
-
// ── Tenant read (requires login) ─────────────────────────────────────────
|
|
113
|
-
|
|
114
114
|
it(
|
|
115
115
|
"S3: invalid task type value is rejected",
|
|
116
116
|
() => {
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { RESULTS } from "@uipath/common";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
type E2EManifest,
|
|
5
|
+
MANIFEST_PATH,
|
|
6
|
+
} from "../../../tests/e2e-shared/global-setup";
|
|
7
|
+
import {
|
|
8
|
+
EXEC_TIMEOUT,
|
|
9
|
+
localCliRaw,
|
|
10
|
+
localCliRawWithArgs,
|
|
11
|
+
parseOutput,
|
|
12
|
+
readManifest,
|
|
13
|
+
} from "../../../tests/e2e-shared/helpers";
|
|
14
|
+
|
|
15
|
+
interface Envelope {
|
|
16
|
+
Result: string;
|
|
17
|
+
Code?: string;
|
|
18
|
+
Data?: unknown;
|
|
19
|
+
Message?: string;
|
|
20
|
+
Instructions?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface TaskRow {
|
|
24
|
+
Id?: number;
|
|
25
|
+
IsDeleted?: boolean;
|
|
26
|
+
Status?: string;
|
|
27
|
+
Type?: string;
|
|
28
|
+
FolderId?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { tempDir } = readManifest<E2EManifest>(MANIFEST_PATH).sharedInstall;
|
|
32
|
+
|
|
33
|
+
function listTasks(asAdmin: boolean): TaskRow[] {
|
|
34
|
+
const flag = asAdmin ? "--as-admin " : "";
|
|
35
|
+
const raw = localCliRaw(
|
|
36
|
+
tempDir,
|
|
37
|
+
`tasks list ${flag}--limit 200 --output json`,
|
|
38
|
+
);
|
|
39
|
+
if (raw.exitCode !== 0) return [];
|
|
40
|
+
return (parseOutput(raw.stdout, "json") as { Data?: TaskRow[] }).Data ?? [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function discoverTask(): { taskId: string; folderId: string } | null {
|
|
44
|
+
const envFolder = process.env.TASKS_TOOL_E2E_FOLDER_ID;
|
|
45
|
+
const envTask =
|
|
46
|
+
process.env.TASKS_TOOL_E2E_TASK_ID_DATA ??
|
|
47
|
+
process.env.TASKS_TOOL_E2E_TASK_ID;
|
|
48
|
+
if (envFolder && envTask) {
|
|
49
|
+
return { taskId: envTask, folderId: envFolder };
|
|
50
|
+
}
|
|
51
|
+
for (const asAdmin of [false, true]) {
|
|
52
|
+
const task = listTasks(asAdmin).find(
|
|
53
|
+
(t) =>
|
|
54
|
+
!t.IsDeleted &&
|
|
55
|
+
t.Status !== "Completed" &&
|
|
56
|
+
t.Type === "ExternalTask" &&
|
|
57
|
+
typeof t.Id === "number" &&
|
|
58
|
+
typeof t.FolderId === "number",
|
|
59
|
+
);
|
|
60
|
+
if (task) {
|
|
61
|
+
return {
|
|
62
|
+
taskId: String(task.Id),
|
|
63
|
+
folderId: String(envFolder ?? task.FolderId),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// No actionable ExternalTask on this tenant; skip the live suite (guarded by describe.skipIf below).
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const discovered = discoverTask();
|
|
72
|
+
const taskId = discovered?.taskId ?? "";
|
|
73
|
+
const folderId = discovered?.folderId ?? "";
|
|
74
|
+
|
|
75
|
+
function getData(id: string): Envelope {
|
|
76
|
+
const raw = localCliRaw(
|
|
77
|
+
tempDir,
|
|
78
|
+
`tasks data get ${id} --folder-id ${folderId} --output json`,
|
|
79
|
+
);
|
|
80
|
+
return parseOutput(raw.stdout, "json") as Envelope;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function saveData(id: string, dataJson: string): Envelope {
|
|
84
|
+
const raw = localCliRawWithArgs(tempDir, [
|
|
85
|
+
"tasks",
|
|
86
|
+
"data",
|
|
87
|
+
"save",
|
|
88
|
+
id,
|
|
89
|
+
"--folder-id",
|
|
90
|
+
folderId,
|
|
91
|
+
"--data",
|
|
92
|
+
dataJson,
|
|
93
|
+
"--output",
|
|
94
|
+
"json",
|
|
95
|
+
]);
|
|
96
|
+
return parseOutput(raw.stdout, "json") as Envelope;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
describe.skipIf(!discovered)("tasks data e2e", () => {
|
|
100
|
+
it(
|
|
101
|
+
"S1: get task data",
|
|
102
|
+
() => {
|
|
103
|
+
const res = getData(taskId);
|
|
104
|
+
expect(res.Result).toBe(RESULTS.Success);
|
|
105
|
+
expect(res.Code).toBe("TaskData");
|
|
106
|
+
expect((res.Data as { Id?: number }).Id).toBe(Number(taskId));
|
|
107
|
+
},
|
|
108
|
+
EXEC_TIMEOUT,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
it(
|
|
112
|
+
"S2: get a non-existent task reports a failure",
|
|
113
|
+
() => {
|
|
114
|
+
const res = getData("999999999");
|
|
115
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
116
|
+
expect(res.Message).toMatch(/^Error getting data for task/);
|
|
117
|
+
},
|
|
118
|
+
EXEC_TIMEOUT,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
it(
|
|
122
|
+
"S3: invalid task id reports a validation error",
|
|
123
|
+
() => {
|
|
124
|
+
const res = getData("not-a-number");
|
|
125
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
126
|
+
expect(res.Message).toMatch(/^Invalid <task-id>/);
|
|
127
|
+
expect(res.Instructions).toBe("Must be a positive integer.");
|
|
128
|
+
},
|
|
129
|
+
EXEC_TIMEOUT,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
it(
|
|
133
|
+
"S4: save task data",
|
|
134
|
+
() => {
|
|
135
|
+
const res = saveData(taskId, '{"amount":1200}');
|
|
136
|
+
expect(res.Result).toBe(RESULTS.Success);
|
|
137
|
+
expect(res.Code).toBe("TaskDataSaved");
|
|
138
|
+
},
|
|
139
|
+
EXEC_TIMEOUT,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
it(
|
|
143
|
+
"S5: invalid data JSON reports a validation error",
|
|
144
|
+
() => {
|
|
145
|
+
const res = saveData(taskId, "not-json");
|
|
146
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
147
|
+
expect(res.Message).toBe("Invalid JSON in --data");
|
|
148
|
+
},
|
|
149
|
+
EXEC_TIMEOUT,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
it(
|
|
153
|
+
"S6: save on a non-existent task reports a failure",
|
|
154
|
+
() => {
|
|
155
|
+
const res = saveData("999999999", '{"amount":1}');
|
|
156
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
157
|
+
expect(res.Message).toMatch(/^Error saving data for task/);
|
|
158
|
+
},
|
|
159
|
+
EXEC_TIMEOUT,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
it(
|
|
163
|
+
"S7: non-object data is rejected before the API call",
|
|
164
|
+
() => {
|
|
165
|
+
const res = saveData(taskId, "[1,2,3]");
|
|
166
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
167
|
+
expect(res.Message).toBe("--data must be a JSON object");
|
|
168
|
+
},
|
|
169
|
+
EXEC_TIMEOUT,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
it(
|
|
173
|
+
"S8: data round-trips through save and get",
|
|
174
|
+
() => {
|
|
175
|
+
const marker = `sdk-cli-e2e-${Date.now()}`;
|
|
176
|
+
const save = saveData(taskId, `{"Marker":"${marker}"}`);
|
|
177
|
+
expect(save.Result).toBe(RESULTS.Success);
|
|
178
|
+
|
|
179
|
+
const res = getData(taskId);
|
|
180
|
+
expect(res.Result).toBe(RESULTS.Success);
|
|
181
|
+
const payload = (res.Data as { Data?: Record<string, unknown> })
|
|
182
|
+
.Data;
|
|
183
|
+
expect(payload?.Marker).toBe(marker);
|
|
184
|
+
},
|
|
185
|
+
EXEC_TIMEOUT,
|
|
186
|
+
);
|
|
187
|
+
});
|
package/tests/get.e2e.test.ts
CHANGED
|
@@ -82,7 +82,7 @@ describe("tasks get e2e", () => {
|
|
|
82
82
|
);
|
|
83
83
|
|
|
84
84
|
it(
|
|
85
|
-
"S4: --task-type without
|
|
85
|
+
"S4: --task-type without a folder option is rejected",
|
|
86
86
|
() => {
|
|
87
87
|
const raw = localCliRaw(
|
|
88
88
|
tempDir,
|
|
@@ -92,9 +92,11 @@ describe("tasks get e2e", () => {
|
|
|
92
92
|
const res = parseOutput(raw.stdout, "json") as GetEnvelope;
|
|
93
93
|
expect(res.Result).toBe(RESULTS.Failure);
|
|
94
94
|
expect(res.Message).toBe(
|
|
95
|
-
"
|
|
95
|
+
"A folder is required when --task-type is specified",
|
|
96
96
|
);
|
|
97
97
|
expect(res.Instructions).toMatch(/--folder-id/);
|
|
98
|
+
expect(res.Instructions).toMatch(/--folder-path/);
|
|
99
|
+
expect(res.Instructions).toMatch(/--folder-key/);
|
|
98
100
|
},
|
|
99
101
|
EXEC_TIMEOUT,
|
|
100
102
|
);
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { RESULTS } from "@uipath/common";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
type E2EManifest,
|
|
5
|
+
MANIFEST_PATH,
|
|
6
|
+
} from "../../../tests/e2e-shared/global-setup";
|
|
7
|
+
import {
|
|
8
|
+
EXEC_TIMEOUT,
|
|
9
|
+
localCliRaw,
|
|
10
|
+
localCliRawWithArgs,
|
|
11
|
+
parseOutput,
|
|
12
|
+
readManifest,
|
|
13
|
+
} from "../../../tests/e2e-shared/helpers";
|
|
14
|
+
|
|
15
|
+
interface Envelope {
|
|
16
|
+
Result: string;
|
|
17
|
+
Code?: string;
|
|
18
|
+
Message?: string;
|
|
19
|
+
Instructions?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface TaskRow {
|
|
23
|
+
Id?: number;
|
|
24
|
+
IsDeleted?: boolean;
|
|
25
|
+
Status?: string;
|
|
26
|
+
Type?: string;
|
|
27
|
+
FolderId?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { tempDir } = readManifest<E2EManifest>(MANIFEST_PATH).sharedInstall;
|
|
31
|
+
|
|
32
|
+
function listTasks(asAdmin: boolean): TaskRow[] {
|
|
33
|
+
const flag = asAdmin ? "--as-admin " : "";
|
|
34
|
+
const raw = localCliRaw(
|
|
35
|
+
tempDir,
|
|
36
|
+
`tasks list ${flag}--limit 200 --output json`,
|
|
37
|
+
);
|
|
38
|
+
if (raw.exitCode !== 0) return [];
|
|
39
|
+
return (parseOutput(raw.stdout, "json") as { Data?: TaskRow[] }).Data ?? [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function discoverTask(): { taskId: string; folderId: string } | null {
|
|
43
|
+
const envFolder = process.env.TASKS_TOOL_E2E_FOLDER_ID;
|
|
44
|
+
const envTask =
|
|
45
|
+
process.env.TASKS_TOOL_E2E_TASK_ID_LABELS ??
|
|
46
|
+
process.env.TASKS_TOOL_E2E_TASK_ID;
|
|
47
|
+
if (envFolder && envTask) {
|
|
48
|
+
return { taskId: envTask, folderId: envFolder };
|
|
49
|
+
}
|
|
50
|
+
for (const asAdmin of [false, true]) {
|
|
51
|
+
const task = listTasks(asAdmin).find(
|
|
52
|
+
(t) =>
|
|
53
|
+
!t.IsDeleted &&
|
|
54
|
+
t.Status !== "Completed" &&
|
|
55
|
+
t.Type === "ExternalTask" &&
|
|
56
|
+
typeof t.Id === "number" &&
|
|
57
|
+
typeof t.FolderId === "number",
|
|
58
|
+
);
|
|
59
|
+
if (task) {
|
|
60
|
+
return {
|
|
61
|
+
taskId: String(task.Id),
|
|
62
|
+
folderId: String(envFolder ?? task.FolderId),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// No actionable ExternalTask on this tenant; skip the live suite (guarded by describe.skipIf below).
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const discovered = discoverTask();
|
|
71
|
+
const taskId = discovered?.taskId ?? "";
|
|
72
|
+
const folderId = discovered?.folderId ?? "";
|
|
73
|
+
|
|
74
|
+
function setTags(id: string, tagsJson: string): Envelope {
|
|
75
|
+
const raw = localCliRawWithArgs(tempDir, [
|
|
76
|
+
"tasks",
|
|
77
|
+
"labels",
|
|
78
|
+
id,
|
|
79
|
+
"--folder-id",
|
|
80
|
+
folderId,
|
|
81
|
+
"--labels",
|
|
82
|
+
tagsJson,
|
|
83
|
+
"--output",
|
|
84
|
+
"json",
|
|
85
|
+
]);
|
|
86
|
+
return parseOutput(raw.stdout, "json") as Envelope;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe.skipIf(!discovered)("tasks labels e2e", () => {
|
|
90
|
+
it(
|
|
91
|
+
"S1: set labels on a task",
|
|
92
|
+
() => {
|
|
93
|
+
const res = setTags(taskId, '[{"name":"urgent"}]');
|
|
94
|
+
expect(res.Result).toBe(RESULTS.Success);
|
|
95
|
+
expect(res.Code).toBe("TaskLabelsSaved");
|
|
96
|
+
},
|
|
97
|
+
EXEC_TIMEOUT,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
it(
|
|
101
|
+
"S2: clear labels with an empty array",
|
|
102
|
+
() => {
|
|
103
|
+
const res = setTags(taskId, "[]");
|
|
104
|
+
expect(res.Result).toBe(RESULTS.Success);
|
|
105
|
+
expect(res.Code).toBe("TaskLabelsSaved");
|
|
106
|
+
},
|
|
107
|
+
EXEC_TIMEOUT,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
it(
|
|
111
|
+
"S3: invalid task id reports a validation error",
|
|
112
|
+
() => {
|
|
113
|
+
const res = setTags("not-a-number", "[]");
|
|
114
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
115
|
+
expect(res.Message).toMatch(/^Invalid <task-id>/);
|
|
116
|
+
expect(res.Instructions).toBe("Must be a positive integer.");
|
|
117
|
+
},
|
|
118
|
+
EXEC_TIMEOUT,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
it(
|
|
122
|
+
"S4: set labels on a non-existent task reports a failure",
|
|
123
|
+
() => {
|
|
124
|
+
const res = setTags("999999999", '[{"name":"urgent"}]');
|
|
125
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
126
|
+
expect(res.Message).toMatch(/^Error saving labels on task/);
|
|
127
|
+
},
|
|
128
|
+
EXEC_TIMEOUT,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
it(
|
|
132
|
+
"S5: invalid JSON in --labels is rejected before the API call",
|
|
133
|
+
() => {
|
|
134
|
+
const res = setTags(taskId, "not-json");
|
|
135
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
136
|
+
expect(res.Message).toBe("Invalid JSON in --labels");
|
|
137
|
+
},
|
|
138
|
+
EXEC_TIMEOUT,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
it(
|
|
142
|
+
"S6: non-array --labels is rejected before the API call",
|
|
143
|
+
() => {
|
|
144
|
+
const res = setTags(taskId, '{"name":"urgent"}');
|
|
145
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
146
|
+
expect(res.Message).toBe("--labels must be a JSON array");
|
|
147
|
+
},
|
|
148
|
+
EXEC_TIMEOUT,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
it(
|
|
152
|
+
"S7: set a label with a display name and value",
|
|
153
|
+
() => {
|
|
154
|
+
const res = setTags(
|
|
155
|
+
taskId,
|
|
156
|
+
'[{"name":"region","displayName":"Region","displayValue":"EMEA"}]',
|
|
157
|
+
);
|
|
158
|
+
expect(res.Result).toBe(RESULTS.Success);
|
|
159
|
+
expect(res.Code).toBe("TaskLabelsSaved");
|
|
160
|
+
},
|
|
161
|
+
EXEC_TIMEOUT,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
it(
|
|
165
|
+
"S8: labels round-trip through set and read-back",
|
|
166
|
+
() => {
|
|
167
|
+
const marker = `sdkclie2e${Date.now()}`;
|
|
168
|
+
const set = setTags(taskId, `[{"name":"${marker}"}]`);
|
|
169
|
+
expect(set.Result).toBe(RESULTS.Success);
|
|
170
|
+
|
|
171
|
+
const getRaw = localCliRaw(
|
|
172
|
+
tempDir,
|
|
173
|
+
`tasks get ${taskId} --output json`,
|
|
174
|
+
);
|
|
175
|
+
const task = parseOutput(getRaw.stdout, "json") as {
|
|
176
|
+
Data?: { Tags?: { Name?: string }[] };
|
|
177
|
+
};
|
|
178
|
+
expect((task.Data?.Tags ?? []).some((t) => t.Name === marker)).toBe(
|
|
179
|
+
true,
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
EXEC_TIMEOUT,
|
|
183
|
+
);
|
|
184
|
+
});
|
package/tests/list.e2e.test.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
EXEC_TIMEOUT,
|
|
9
9
|
localCliRaw,
|
|
10
|
+
localCliRawWithArgs,
|
|
10
11
|
parseOutput,
|
|
11
12
|
readManifest,
|
|
12
13
|
} from "../../../tests/e2e-shared/helpers";
|
|
@@ -39,6 +40,25 @@ function discoverFolderId(): string | undefined {
|
|
|
39
40
|
return typeof folderId === "number" ? String(folderId) : undefined;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
interface FolderRow {
|
|
44
|
+
Key?: string;
|
|
45
|
+
Name?: string;
|
|
46
|
+
Path?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Discover a folder's path and key via `uip or folders list` — the exact
|
|
51
|
+
* source UV-15957 is about: its output has no numeric ID column, so path/key
|
|
52
|
+
* are what a caller actually holds.
|
|
53
|
+
*/
|
|
54
|
+
function discoverFolder(): { path?: string; key?: string } {
|
|
55
|
+
const raw = localCliRaw(tempDir, "or folders list --output json");
|
|
56
|
+
if (raw.exitCode !== 0) return {};
|
|
57
|
+
const res = parseOutput(raw.stdout, "json") as { Data?: FolderRow[] };
|
|
58
|
+
const first = res.Data?.[0];
|
|
59
|
+
return { path: first?.Path || first?.Name, key: first?.Key };
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
describe("tasks list e2e", () => {
|
|
43
63
|
it(
|
|
44
64
|
"S1: lists tasks across all folders",
|
|
@@ -121,4 +141,94 @@ describe("tasks list e2e", () => {
|
|
|
121
141
|
},
|
|
122
142
|
EXEC_TIMEOUT,
|
|
123
143
|
);
|
|
144
|
+
|
|
145
|
+
it(
|
|
146
|
+
"S6: filters by folder path",
|
|
147
|
+
() => {
|
|
148
|
+
const { path } = discoverFolder();
|
|
149
|
+
if (!path) return;
|
|
150
|
+
// API-derived value — pass as its own argument, never through a
|
|
151
|
+
// shell-interpolated string.
|
|
152
|
+
const raw = localCliRawWithArgs(tempDir, [
|
|
153
|
+
"tasks",
|
|
154
|
+
"list",
|
|
155
|
+
"--folder-path",
|
|
156
|
+
path,
|
|
157
|
+
"--output",
|
|
158
|
+
"json",
|
|
159
|
+
]);
|
|
160
|
+
const res = parseOutput(raw.stdout, "json") as ListEnvelope;
|
|
161
|
+
expect(raw.exitCode, JSON.stringify(res)).toBe(0);
|
|
162
|
+
expect(res.Result).toBe(RESULTS.Success);
|
|
163
|
+
expect(res.Code).toBe("TaskList");
|
|
164
|
+
expect(Array.isArray(res.Data)).toBe(true);
|
|
165
|
+
},
|
|
166
|
+
EXEC_TIMEOUT,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
it(
|
|
170
|
+
"S7: filters by folder key",
|
|
171
|
+
() => {
|
|
172
|
+
const { key } = discoverFolder();
|
|
173
|
+
if (!key) return;
|
|
174
|
+
const raw = localCliRaw(
|
|
175
|
+
tempDir,
|
|
176
|
+
`tasks list --folder-key ${key} --output json`,
|
|
177
|
+
);
|
|
178
|
+
const res = parseOutput(raw.stdout, "json") as ListEnvelope;
|
|
179
|
+
expect(raw.exitCode, JSON.stringify(res)).toBe(0);
|
|
180
|
+
expect(res.Result).toBe(RESULTS.Success);
|
|
181
|
+
expect(res.Code).toBe("TaskList");
|
|
182
|
+
expect(Array.isArray(res.Data)).toBe(true);
|
|
183
|
+
},
|
|
184
|
+
EXEC_TIMEOUT,
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
it(
|
|
188
|
+
"S8: folder options are mutually exclusive",
|
|
189
|
+
() => {
|
|
190
|
+
const raw = localCliRaw(
|
|
191
|
+
tempDir,
|
|
192
|
+
"tasks list --folder-id 1 --folder-path Shared --output json",
|
|
193
|
+
);
|
|
194
|
+
expect(raw.exitCode).not.toBe(0);
|
|
195
|
+
const res = parseOutput(raw.stdout, "json") as ListEnvelope;
|
|
196
|
+
expect(res.Message).toMatch(/cannot be used with/);
|
|
197
|
+
},
|
|
198
|
+
EXEC_TIMEOUT,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
it(
|
|
202
|
+
"S9: invalid --folder-key reports a validation error",
|
|
203
|
+
() => {
|
|
204
|
+
const raw = localCliRaw(
|
|
205
|
+
tempDir,
|
|
206
|
+
"tasks list --folder-key not-a-guid --output json",
|
|
207
|
+
);
|
|
208
|
+
expect(raw.exitCode).not.toBe(0);
|
|
209
|
+
const res = parseOutput(raw.stdout, "json") as ListEnvelope;
|
|
210
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
211
|
+
expect(res.Message).toBe("Invalid --folder-key: not-a-guid");
|
|
212
|
+
expect(res.Instructions).toMatch(/UUID/);
|
|
213
|
+
},
|
|
214
|
+
EXEC_TIMEOUT,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
it(
|
|
218
|
+
"S10: unknown --folder-path reports folder not found",
|
|
219
|
+
() => {
|
|
220
|
+
const raw = localCliRaw(
|
|
221
|
+
tempDir,
|
|
222
|
+
'tasks list --folder-path "NoSuchFolder-UV15957" --output json',
|
|
223
|
+
);
|
|
224
|
+
expect(raw.exitCode).not.toBe(0);
|
|
225
|
+
const res = parseOutput(raw.stdout, "json") as ListEnvelope;
|
|
226
|
+
expect(res.Result).toBe(RESULTS.Failure);
|
|
227
|
+
expect(res.Message).toBe(
|
|
228
|
+
"Folder not found: 'NoSuchFolder-UV15957'.",
|
|
229
|
+
);
|
|
230
|
+
expect(res.Instructions).toMatch(/uip or folders list/);
|
|
231
|
+
},
|
|
232
|
+
EXEC_TIMEOUT,
|
|
233
|
+
);
|
|
124
234
|
});
|