@fluidframework/task-manager 2.0.0-rc.1.0.4 → 2.0.0-rc.2.0.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.
- package/{.eslintrc.js → .eslintrc.cjs} +5 -9
- package/{.mocharc.js → .mocharc.cjs} +1 -1
- package/CHANGELOG.md +4 -0
- package/{api-extractor-esm.json → api-extractor-cjs.json} +5 -1
- package/api-extractor-lint.json +1 -1
- package/api-extractor.json +1 -1
- package/api-report/task-manager.api.md +1 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/package.json +3 -0
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/task-manager-beta.d.ts +2 -0
- package/dist/task-manager-public.d.ts +2 -0
- package/dist/task-manager-untrimmed.d.ts +1 -1
- package/dist/taskManager.d.ts +2 -2
- package/dist/taskManager.d.ts.map +1 -1
- package/dist/taskManager.js +27 -11
- package/dist/taskManager.js.map +1 -1
- package/dist/taskManagerFactory.d.ts +1 -1
- package/dist/taskManagerFactory.d.ts.map +1 -1
- package/dist/taskManagerFactory.js +5 -5
- package/dist/taskManagerFactory.js.map +1 -1
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/index.d.ts +13 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/{index.mjs → index.js} +2 -2
- package/lib/index.js.map +1 -0
- package/lib/{interfaces.d.mts → interfaces.d.ts} +1 -1
- package/lib/interfaces.d.ts.map +1 -0
- package/lib/{interfaces.mjs → interfaces.js} +1 -1
- package/lib/interfaces.js.map +1 -0
- package/lib/{packageVersion.d.mts → packageVersion.d.ts} +2 -2
- package/lib/packageVersion.d.ts.map +1 -0
- package/lib/{packageVersion.mjs → packageVersion.js} +2 -2
- package/lib/packageVersion.js.map +1 -0
- package/lib/{task-manager-alpha.d.mts → task-manager-alpha.d.ts} +7 -0
- package/lib/{task-manager-beta.d.mts → task-manager-beta.d.ts} +9 -0
- package/lib/{task-manager-public.d.mts → task-manager-public.d.ts} +9 -0
- package/lib/{task-manager-untrimmed.d.mts → task-manager-untrimmed.d.ts} +8 -1
- package/lib/{taskManager.d.mts → taskManager.d.ts} +3 -3
- package/lib/taskManager.d.ts.map +1 -0
- package/lib/{taskManager.mjs → taskManager.js} +22 -6
- package/lib/taskManager.js.map +1 -0
- package/lib/{taskManagerFactory.d.mts → taskManagerFactory.d.ts} +2 -2
- package/lib/taskManagerFactory.d.ts.map +1 -0
- package/lib/{taskManagerFactory.mjs → taskManagerFactory.js} +3 -3
- package/lib/taskManagerFactory.js.map +1 -0
- package/lib/test/dirname.cjs +16 -0
- package/lib/test/dirname.cjs.map +1 -0
- package/lib/test/taskManager.fuzz.spec.js +203 -0
- package/lib/test/taskManager.fuzz.spec.js.map +1 -0
- package/lib/test/taskManager.spec.js +845 -0
- package/lib/test/taskManager.spec.js.map +1 -0
- package/lib/test/types/validateTaskManagerPrevious.generated.js +12 -0
- package/lib/test/types/validateTaskManagerPrevious.generated.js.map +1 -0
- package/package.json +44 -57
- package/src/index.ts +2 -2
- package/src/packageVersion.ts +1 -1
- package/src/taskManager.ts +22 -7
- package/src/taskManagerFactory.ts +3 -3
- package/tsconfig.cjs.json +7 -0
- package/tsconfig.json +2 -5
- package/lib/index.d.mts +0 -7
- package/lib/index.d.mts.map +0 -1
- package/lib/index.mjs.map +0 -1
- package/lib/interfaces.d.mts.map +0 -1
- package/lib/interfaces.mjs.map +0 -1
- package/lib/packageVersion.d.mts.map +0 -1
- package/lib/packageVersion.mjs.map +0 -1
- package/lib/taskManager.d.mts.map +0 -1
- package/lib/taskManager.mjs.map +0 -1
- package/lib/taskManagerFactory.d.mts.map +0 -1
- package/lib/taskManagerFactory.mjs.map +0 -1
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import { strict as assert } from "assert";
|
|
6
|
+
import { MockFluidDataStoreRuntime, MockContainerRuntimeFactory, MockContainerRuntimeFactoryForReconnection, MockStorage, } from "@fluidframework/test-runtime-utils";
|
|
7
|
+
import { AttachState } from "@fluidframework/container-definitions";
|
|
8
|
+
import { TaskManager } from "../taskManager.js";
|
|
9
|
+
import { TaskManagerFactory } from "../taskManagerFactory.js";
|
|
10
|
+
function createConnectedTaskManager(id, runtimeFactory) {
|
|
11
|
+
// Create and connect a TaskManager.
|
|
12
|
+
const dataStoreRuntime = new MockFluidDataStoreRuntime();
|
|
13
|
+
runtimeFactory.createContainerRuntime(dataStoreRuntime);
|
|
14
|
+
const services = {
|
|
15
|
+
deltaConnection: dataStoreRuntime.createDeltaConnection(),
|
|
16
|
+
objectStorage: new MockStorage(),
|
|
17
|
+
};
|
|
18
|
+
const taskManager = new TaskManager(id, dataStoreRuntime, TaskManagerFactory.Attributes);
|
|
19
|
+
taskManager.connect(services);
|
|
20
|
+
return taskManager;
|
|
21
|
+
}
|
|
22
|
+
function createDetachedTaskManager(id, runtimeFactory) {
|
|
23
|
+
// Create a detached TaskManager.
|
|
24
|
+
const dataStoreRuntime = new MockFluidDataStoreRuntime({ attachState: AttachState.Detached });
|
|
25
|
+
runtimeFactory.createContainerRuntime(dataStoreRuntime);
|
|
26
|
+
const clientId = dataStoreRuntime.clientId;
|
|
27
|
+
const taskManager = new TaskManager(id, dataStoreRuntime, TaskManagerFactory.Attributes);
|
|
28
|
+
const attach = async () => {
|
|
29
|
+
const services = {
|
|
30
|
+
deltaConnection: dataStoreRuntime.createDeltaConnection(),
|
|
31
|
+
objectStorage: new MockStorage(),
|
|
32
|
+
};
|
|
33
|
+
// Manually trigger a summarize (should be done automatically when attaching normally)
|
|
34
|
+
await taskManager.summarize();
|
|
35
|
+
dataStoreRuntime.setAttachState(AttachState.Attached);
|
|
36
|
+
taskManager.connect(services);
|
|
37
|
+
// Ensure clientId is set after attach (might be forced undefined in some tests)
|
|
38
|
+
dataStoreRuntime.clientId = clientId;
|
|
39
|
+
};
|
|
40
|
+
return { taskManager, attach };
|
|
41
|
+
}
|
|
42
|
+
describe("TaskManager", () => {
|
|
43
|
+
describe("Connected state", () => {
|
|
44
|
+
let taskManager1;
|
|
45
|
+
let taskManager2;
|
|
46
|
+
let containerRuntimeFactory;
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
containerRuntimeFactory = new MockContainerRuntimeFactory();
|
|
49
|
+
taskManager1 = createConnectedTaskManager("taskManager1", containerRuntimeFactory);
|
|
50
|
+
taskManager2 = createConnectedTaskManager("taskManager2", containerRuntimeFactory);
|
|
51
|
+
});
|
|
52
|
+
it("Can create a connected TaskManager", () => {
|
|
53
|
+
assert.ok(taskManager1, "Could not create a task manager");
|
|
54
|
+
assert.ok(taskManager1.isAttached(), "TaskManager should be attached");
|
|
55
|
+
assert.ok(taskManager1.connected, "TaskManager should be connected");
|
|
56
|
+
});
|
|
57
|
+
describe("Volunteering for a task", () => {
|
|
58
|
+
it("Can volunteer for a task", async () => {
|
|
59
|
+
const taskId = "taskId";
|
|
60
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
61
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
62
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
63
|
+
containerRuntimeFactory.processAllMessages();
|
|
64
|
+
const isAssigned = await volunteerTaskP;
|
|
65
|
+
assert.ok(isAssigned, "Should resolve true");
|
|
66
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
67
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
68
|
+
});
|
|
69
|
+
it("Can wait for a task", async () => {
|
|
70
|
+
const taskId = "taskId";
|
|
71
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
72
|
+
const volunteerTaskP2 = taskManager2.volunteerForTask(taskId);
|
|
73
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
74
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
75
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
76
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
77
|
+
containerRuntimeFactory.processAllMessages();
|
|
78
|
+
const isAssigned1 = await volunteerTaskP1;
|
|
79
|
+
assert.ok(isAssigned1, "Should resolve true");
|
|
80
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
81
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
82
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
83
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
84
|
+
taskManager1.abandon(taskId);
|
|
85
|
+
containerRuntimeFactory.processAllMessages();
|
|
86
|
+
const isAssigned2 = await volunteerTaskP2;
|
|
87
|
+
assert.ok(isAssigned2, "Should resolve true");
|
|
88
|
+
assert.ok(!taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
89
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
90
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
91
|
+
assert.ok(taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
92
|
+
});
|
|
93
|
+
it("Rejects the promise if abandon before ack", async () => {
|
|
94
|
+
const taskId = "taskId";
|
|
95
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
96
|
+
taskManager1.abandon(taskId);
|
|
97
|
+
// Will reject due to exiting the queue without first acquiring task
|
|
98
|
+
// Promise should be settled already prior to processing messages
|
|
99
|
+
await assert.rejects(volunteerTaskP);
|
|
100
|
+
containerRuntimeFactory.processAllMessages();
|
|
101
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
102
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
103
|
+
});
|
|
104
|
+
it("Rejects the promise if abandon after ack but before acquire", async () => {
|
|
105
|
+
const taskId = "taskId";
|
|
106
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
107
|
+
const volunteerTaskP2 = taskManager2.volunteerForTask(taskId);
|
|
108
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
109
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
110
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
111
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
112
|
+
containerRuntimeFactory.processAllMessages();
|
|
113
|
+
const isAssigned = await volunteerTaskP1;
|
|
114
|
+
assert.ok(isAssigned, "Should resolve true");
|
|
115
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
116
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
117
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
118
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
119
|
+
taskManager2.abandon(taskId);
|
|
120
|
+
// Will reject due to exiting the queue without first acquiring task
|
|
121
|
+
// Promise should be settled already prior to processing messages
|
|
122
|
+
await assert.rejects(volunteerTaskP2);
|
|
123
|
+
containerRuntimeFactory.processAllMessages();
|
|
124
|
+
assert.ok(!taskManager2.queued(taskId), "Should not be queued");
|
|
125
|
+
assert.ok(!taskManager2.assigned(taskId), "Should not be assigned");
|
|
126
|
+
});
|
|
127
|
+
it("Can abandon and immediately attempt to reacquire a task", async () => {
|
|
128
|
+
const taskId = "taskId";
|
|
129
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
130
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
131
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
132
|
+
containerRuntimeFactory.processAllMessages();
|
|
133
|
+
const isAssigned = await volunteerTaskP;
|
|
134
|
+
assert.ok(isAssigned, "Should resolve true");
|
|
135
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
136
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
137
|
+
taskManager1.abandon(taskId);
|
|
138
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
139
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
140
|
+
const revolunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
141
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
142
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
143
|
+
containerRuntimeFactory.processAllMessages();
|
|
144
|
+
const isAssigned2 = await revolunteerTaskP;
|
|
145
|
+
assert.ok(isAssigned2, "Should resolve true");
|
|
146
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
147
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
148
|
+
});
|
|
149
|
+
it("Can attempt to volunteer for task twice and abandon twice (after ack)", async () => {
|
|
150
|
+
const taskId = "taskId";
|
|
151
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
152
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
153
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
154
|
+
containerRuntimeFactory.processAllMessages();
|
|
155
|
+
const isAssigned1 = await volunteerTaskP1;
|
|
156
|
+
assert.ok(isAssigned1, "Should resolve true");
|
|
157
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
158
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
159
|
+
const volunteerTaskP2 = taskManager1.volunteerForTask(taskId);
|
|
160
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
161
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
162
|
+
containerRuntimeFactory.processAllMessages();
|
|
163
|
+
const isAssigned2 = await volunteerTaskP2;
|
|
164
|
+
assert.ok(isAssigned2, "Should resolve true");
|
|
165
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
166
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
167
|
+
taskManager1.abandon(taskId);
|
|
168
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
169
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
170
|
+
containerRuntimeFactory.processAllMessages();
|
|
171
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
172
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
173
|
+
taskManager1.abandon(taskId);
|
|
174
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
175
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
176
|
+
containerRuntimeFactory.processAllMessages();
|
|
177
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
178
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
179
|
+
});
|
|
180
|
+
it("Can attempt to lock task twice and abandon twice (before ack)", async () => {
|
|
181
|
+
const taskId = "taskId";
|
|
182
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
183
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
184
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
185
|
+
const volunteerTaskP2 = taskManager1.volunteerForTask(taskId);
|
|
186
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
187
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
188
|
+
containerRuntimeFactory.processAllMessages();
|
|
189
|
+
const isAssigned1 = await volunteerTaskP1;
|
|
190
|
+
assert.ok(isAssigned1, "Should resolve true");
|
|
191
|
+
const isAssigned2 = await volunteerTaskP2;
|
|
192
|
+
assert.ok(isAssigned2, "Should resolve true");
|
|
193
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
194
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
195
|
+
taskManager1.abandon(taskId);
|
|
196
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
197
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
198
|
+
taskManager1.abandon(taskId);
|
|
199
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
200
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
201
|
+
containerRuntimeFactory.processAllMessages();
|
|
202
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
203
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
204
|
+
});
|
|
205
|
+
it("Can volunteer for a task immediately after it was completed", async () => {
|
|
206
|
+
const taskId = "taskId";
|
|
207
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
208
|
+
containerRuntimeFactory.processAllMessages();
|
|
209
|
+
await volunteerTaskP;
|
|
210
|
+
taskManager2.subscribeToTask(taskId);
|
|
211
|
+
taskManager1.complete(taskId);
|
|
212
|
+
const volunteerTaskP2 = taskManager1.volunteerForTask(taskId);
|
|
213
|
+
containerRuntimeFactory.processAllMessages();
|
|
214
|
+
await volunteerTaskP2;
|
|
215
|
+
assert.ok(taskManager1.assigned(taskId), "taskManager1 should be assigned");
|
|
216
|
+
assert.ok(!taskManager2.queued(taskId), "taskManager 2 should not be assigned");
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
describe("Subscribing to a task", () => {
|
|
220
|
+
it("Can subscribe to a task", async () => {
|
|
221
|
+
const taskId = "taskId";
|
|
222
|
+
taskManager1.subscribeToTask(taskId);
|
|
223
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
224
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
225
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
226
|
+
containerRuntimeFactory.processAllMessages();
|
|
227
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
228
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
229
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
230
|
+
});
|
|
231
|
+
it("Can abandon a subscribed task", async () => {
|
|
232
|
+
const taskId = "taskId";
|
|
233
|
+
taskManager1.subscribeToTask(taskId);
|
|
234
|
+
containerRuntimeFactory.processAllMessages();
|
|
235
|
+
taskManager1.abandon(taskId);
|
|
236
|
+
containerRuntimeFactory.processAllMessages();
|
|
237
|
+
assert.ok(!taskManager1.queued(taskId), "Task manager 1 should not be queued");
|
|
238
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
239
|
+
assert.ok(!taskManager1.subscribed(taskId), "Task manager 1 should not be subscribed");
|
|
240
|
+
});
|
|
241
|
+
it("Can subscribe and wait for a task", async () => {
|
|
242
|
+
const taskId = "taskId";
|
|
243
|
+
taskManager1.subscribeToTask(taskId);
|
|
244
|
+
taskManager2.subscribeToTask(taskId);
|
|
245
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
246
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
247
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
248
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
249
|
+
containerRuntimeFactory.processAllMessages();
|
|
250
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
251
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
252
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
253
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
254
|
+
taskManager1.abandon(taskId);
|
|
255
|
+
containerRuntimeFactory.processAllMessages();
|
|
256
|
+
assert.ok(taskManager2.assigned(taskId), "Task manager 2 should be assigned");
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
describe("Completing tasks", () => {
|
|
260
|
+
it("Can complete a task", async () => {
|
|
261
|
+
const taskId = "taskId";
|
|
262
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
263
|
+
containerRuntimeFactory.processAllMessages();
|
|
264
|
+
await volunteerTaskP;
|
|
265
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
266
|
+
taskManager1.complete(taskId);
|
|
267
|
+
containerRuntimeFactory.processAllMessages();
|
|
268
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
269
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
270
|
+
});
|
|
271
|
+
it("Rejects the promise if you try to complete without being assigned", async () => {
|
|
272
|
+
const taskId = "taskId";
|
|
273
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
274
|
+
void taskManager2.volunteerForTask(taskId);
|
|
275
|
+
containerRuntimeFactory.processAllMessages();
|
|
276
|
+
await volunteerTaskP1;
|
|
277
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
278
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
279
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
280
|
+
assert.throws(() => {
|
|
281
|
+
taskManager2.complete(taskId);
|
|
282
|
+
}, "Should throw error");
|
|
283
|
+
containerRuntimeFactory.processAllMessages();
|
|
284
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
285
|
+
});
|
|
286
|
+
it("Can complete a task and remove other clients from queue", async () => {
|
|
287
|
+
const taskId = "taskId";
|
|
288
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
289
|
+
const volunteerTaskP2 = taskManager2.volunteerForTask(taskId);
|
|
290
|
+
containerRuntimeFactory.processAllMessages();
|
|
291
|
+
const isAssigned1 = await volunteerTaskP1;
|
|
292
|
+
assert.ok(isAssigned1, "Should resolve true");
|
|
293
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
294
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
295
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
296
|
+
taskManager1.complete(taskId);
|
|
297
|
+
containerRuntimeFactory.processAllMessages();
|
|
298
|
+
const isAssigned2 = await volunteerTaskP2;
|
|
299
|
+
assert.ok(!isAssigned2, "Should resolve false");
|
|
300
|
+
assert.ok(!taskManager1.queued(taskId), "Task manager 1 should not be queued");
|
|
301
|
+
assert.ok(!taskManager2.queued(taskId), "Task manager 2 should not be queued");
|
|
302
|
+
});
|
|
303
|
+
it("Can complete a task and remove other subscribed clients from queue", async () => {
|
|
304
|
+
const taskId = "taskId";
|
|
305
|
+
taskManager1.subscribeToTask(taskId);
|
|
306
|
+
taskManager2.subscribeToTask(taskId);
|
|
307
|
+
containerRuntimeFactory.processAllMessages();
|
|
308
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
309
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
310
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
311
|
+
taskManager1.complete(taskId);
|
|
312
|
+
containerRuntimeFactory.processAllMessages();
|
|
313
|
+
assert.ok(!taskManager1.queued(taskId), "Task manager 1 should not be queued");
|
|
314
|
+
assert.ok(!taskManager2.queued(taskId), "Task manager 2 should not be queued");
|
|
315
|
+
});
|
|
316
|
+
it("Can emit completed event", async () => {
|
|
317
|
+
const taskId = "taskId";
|
|
318
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
319
|
+
taskManager2.subscribeToTask(taskId);
|
|
320
|
+
containerRuntimeFactory.processAllMessages();
|
|
321
|
+
await volunteerTaskP1;
|
|
322
|
+
let taskManager1EventFired = false;
|
|
323
|
+
let taskManager2EventFired = false;
|
|
324
|
+
taskManager1.on("completed", (completedTaskId) => {
|
|
325
|
+
assert.ok(completedTaskId === taskId, "taskId should match");
|
|
326
|
+
assert.ok(!taskManager1EventFired, "Should only fire completed event once on taskManager1");
|
|
327
|
+
taskManager1EventFired = true;
|
|
328
|
+
});
|
|
329
|
+
taskManager2.on("completed", (completedTaskId) => {
|
|
330
|
+
assert.ok(completedTaskId === taskId, "taskId should match");
|
|
331
|
+
assert.ok(!taskManager2EventFired, "Should only fire completed event once on taskManager2");
|
|
332
|
+
taskManager2EventFired = true;
|
|
333
|
+
});
|
|
334
|
+
taskManager1.complete(taskId);
|
|
335
|
+
containerRuntimeFactory.processAllMessages();
|
|
336
|
+
assert.ok(taskManager1EventFired, "Should have raised completed event on taskManager1");
|
|
337
|
+
assert.ok(taskManager2EventFired, "Should have raised completed event on taskManager2");
|
|
338
|
+
});
|
|
339
|
+
it("Can complete a task with a pending volunteer op", async () => {
|
|
340
|
+
const taskId = "taskId";
|
|
341
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
342
|
+
containerRuntimeFactory.processAllMessages();
|
|
343
|
+
await volunteerTaskP1;
|
|
344
|
+
let taskManager1EventFired = false;
|
|
345
|
+
let taskManager2EventFired = false;
|
|
346
|
+
taskManager1.on("completed", (completedTaskId) => {
|
|
347
|
+
assert.ok(completedTaskId === taskId, "taskId should match");
|
|
348
|
+
assert.ok(!taskManager1EventFired, "Should only fire completed event once on taskManager1");
|
|
349
|
+
taskManager1EventFired = true;
|
|
350
|
+
});
|
|
351
|
+
taskManager2.on("completed", (completedTaskId) => {
|
|
352
|
+
assert.ok(completedTaskId === taskId, "taskId should match");
|
|
353
|
+
assert.ok(!taskManager2EventFired, "Should only fire completed event once on taskManager2");
|
|
354
|
+
taskManager2EventFired = true;
|
|
355
|
+
});
|
|
356
|
+
const volunteerTaskP2 = taskManager2.volunteerForTask(taskId);
|
|
357
|
+
taskManager1.complete(taskId);
|
|
358
|
+
containerRuntimeFactory.processAllMessages();
|
|
359
|
+
await volunteerTaskP2;
|
|
360
|
+
assert.ok(taskManager1EventFired, "Should have raised completed event on taskManager1");
|
|
361
|
+
assert.ok(taskManager2EventFired, "Should have raised completed event on taskManager2");
|
|
362
|
+
assert.ok(!taskManager1.queued(taskId), "Task manager 1 should not be queued");
|
|
363
|
+
assert.ok(!taskManager2.queued(taskId), "Task manager 2 should not be queued");
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
// Note: Since read/write modes are not yet implemented in mocks, tests are limited to simulate these scenarios.
|
|
368
|
+
describe("Read/Write Mode", () => {
|
|
369
|
+
let taskManager1;
|
|
370
|
+
let containerRuntimeFactory;
|
|
371
|
+
let containerRuntime1;
|
|
372
|
+
const setReadOnlyInfo = (readOnlyInfo) => {
|
|
373
|
+
taskManager1.runtime.deltaManager.readOnlyInfo = readOnlyInfo;
|
|
374
|
+
// Force connection to simulate read mode (TaskManager considered the client disconnected in read mode)
|
|
375
|
+
containerRuntime1.connected = readOnlyInfo.readonly === false;
|
|
376
|
+
};
|
|
377
|
+
beforeEach(() => {
|
|
378
|
+
containerRuntimeFactory = new MockContainerRuntimeFactoryForReconnection();
|
|
379
|
+
const dataStoreRuntime1 = new MockFluidDataStoreRuntime();
|
|
380
|
+
containerRuntime1 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime1);
|
|
381
|
+
const services1 = {
|
|
382
|
+
deltaConnection: dataStoreRuntime1.createDeltaConnection(),
|
|
383
|
+
objectStorage: new MockStorage(),
|
|
384
|
+
};
|
|
385
|
+
taskManager1 = new TaskManager("task-manager-1", dataStoreRuntime1, TaskManagerFactory.Attributes);
|
|
386
|
+
taskManager1.connect(services1);
|
|
387
|
+
});
|
|
388
|
+
it("Immediately rejects attempts to volunteer in read mode", async () => {
|
|
389
|
+
const taskId = "taskId";
|
|
390
|
+
setReadOnlyInfo({
|
|
391
|
+
readonly: true,
|
|
392
|
+
permissions: false,
|
|
393
|
+
forced: false,
|
|
394
|
+
storageOnly: false,
|
|
395
|
+
});
|
|
396
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
397
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
398
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
399
|
+
await assert.rejects(volunteerTaskP);
|
|
400
|
+
containerRuntimeFactory.processAllMessages();
|
|
401
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
402
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
403
|
+
});
|
|
404
|
+
it("Immediately rejects attempts to volunteer with read-only permissions", async () => {
|
|
405
|
+
const taskId = "taskId";
|
|
406
|
+
setReadOnlyInfo({
|
|
407
|
+
readonly: true,
|
|
408
|
+
permissions: true,
|
|
409
|
+
forced: false,
|
|
410
|
+
storageOnly: false,
|
|
411
|
+
});
|
|
412
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
413
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
414
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
415
|
+
await assert.rejects(volunteerTaskP);
|
|
416
|
+
containerRuntimeFactory.processAllMessages();
|
|
417
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
418
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
419
|
+
});
|
|
420
|
+
it("Can subscribe while in read mode", async () => {
|
|
421
|
+
const taskId = "taskId";
|
|
422
|
+
setReadOnlyInfo({
|
|
423
|
+
readonly: true,
|
|
424
|
+
permissions: false,
|
|
425
|
+
forced: false,
|
|
426
|
+
storageOnly: false,
|
|
427
|
+
});
|
|
428
|
+
taskManager1.subscribeToTask(taskId);
|
|
429
|
+
containerRuntimeFactory.processAllMessages();
|
|
430
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
431
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
432
|
+
assert.ok(taskManager1.subscribed(taskId), "Should be subscribed");
|
|
433
|
+
setReadOnlyInfo({ readonly: false });
|
|
434
|
+
containerRuntimeFactory.processAllMessages();
|
|
435
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
436
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
437
|
+
assert.ok(taskManager1.subscribed(taskId), "Should be subscribed");
|
|
438
|
+
});
|
|
439
|
+
it("Immediately rejects attempts to subscribe with read-only permissions", async () => {
|
|
440
|
+
const taskId = "taskId";
|
|
441
|
+
setReadOnlyInfo({
|
|
442
|
+
readonly: true,
|
|
443
|
+
permissions: true,
|
|
444
|
+
forced: false,
|
|
445
|
+
storageOnly: false,
|
|
446
|
+
});
|
|
447
|
+
assert.throws(() => {
|
|
448
|
+
taskManager1.subscribeToTask(taskId);
|
|
449
|
+
}, "Should throw error if subscribing with read-only permissions");
|
|
450
|
+
containerRuntimeFactory.processAllMessages();
|
|
451
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
452
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
453
|
+
assert.ok(!taskManager1.subscribed(taskId), "Should not be subscribed");
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
describe("Detached/Attach", () => {
|
|
457
|
+
let taskManager1;
|
|
458
|
+
let attachTaskManager1;
|
|
459
|
+
// let taskManager2: ITaskManager;
|
|
460
|
+
// let attachTaskManager2: () => void;
|
|
461
|
+
let containerRuntimeFactory;
|
|
462
|
+
const placeholderClientId = "placeholder";
|
|
463
|
+
beforeEach(() => {
|
|
464
|
+
containerRuntimeFactory = new MockContainerRuntimeFactory();
|
|
465
|
+
const createResponse1 = createDetachedTaskManager("taskManager1", containerRuntimeFactory);
|
|
466
|
+
taskManager1 = createResponse1.taskManager;
|
|
467
|
+
attachTaskManager1 = createResponse1.attach;
|
|
468
|
+
});
|
|
469
|
+
it("Can create a detached TaskManager and attach later", async () => {
|
|
470
|
+
assert.ok(!taskManager1.isAttached(), "taskManager1 should be detached");
|
|
471
|
+
await attachTaskManager1();
|
|
472
|
+
assert.ok(taskManager1.isAttached(), "taskManager1 should be attached");
|
|
473
|
+
});
|
|
474
|
+
describe("Behavior before attach", () => {
|
|
475
|
+
describe("Volunteering for a task", () => {
|
|
476
|
+
it("Can volunteer for a task before attach", async () => {
|
|
477
|
+
const taskId = "taskId";
|
|
478
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
479
|
+
containerRuntimeFactory.processAllMessages();
|
|
480
|
+
await volunteerTaskP;
|
|
481
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
482
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
483
|
+
});
|
|
484
|
+
it("Can volunteer and abandon a task before attach", async () => {
|
|
485
|
+
const taskId = "taskId";
|
|
486
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
487
|
+
containerRuntimeFactory.processAllMessages();
|
|
488
|
+
await volunteerTaskP;
|
|
489
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
490
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
491
|
+
taskManager1.abandon(taskId);
|
|
492
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
493
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
describe("Subscribing to a task", () => {
|
|
497
|
+
it("Can subscribe to a task before attach", async () => {
|
|
498
|
+
const taskId = "taskId";
|
|
499
|
+
taskManager1.subscribeToTask(taskId);
|
|
500
|
+
containerRuntimeFactory.processAllMessages();
|
|
501
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
502
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
503
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
504
|
+
});
|
|
505
|
+
it("Can abandon a subscribed task before attach", async () => {
|
|
506
|
+
const taskId = "taskId";
|
|
507
|
+
taskManager1.subscribeToTask(taskId);
|
|
508
|
+
containerRuntimeFactory.processAllMessages();
|
|
509
|
+
taskManager1.abandon(taskId);
|
|
510
|
+
containerRuntimeFactory.processAllMessages();
|
|
511
|
+
assert.ok(!taskManager1.queued(taskId), "Task manager 1 should not be queued");
|
|
512
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
513
|
+
assert.ok(!taskManager1.subscribed(taskId), "Task manager 1 should not be subscribed");
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
describe("Completing tasks", () => {
|
|
517
|
+
it("Can complete a task before attach", async () => {
|
|
518
|
+
const taskId = "taskId";
|
|
519
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
520
|
+
containerRuntimeFactory.processAllMessages();
|
|
521
|
+
await volunteerTaskP;
|
|
522
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
523
|
+
taskManager1.complete(taskId);
|
|
524
|
+
containerRuntimeFactory.processAllMessages();
|
|
525
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
526
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
527
|
+
});
|
|
528
|
+
it("Can emit completed event before attach", async () => {
|
|
529
|
+
const taskId = "taskId";
|
|
530
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
531
|
+
containerRuntimeFactory.processAllMessages();
|
|
532
|
+
await volunteerTaskP1;
|
|
533
|
+
let taskManager1EventFired = false;
|
|
534
|
+
taskManager1.once("completed", (completedTaskId) => {
|
|
535
|
+
assert.ok(completedTaskId === taskId, "taskId should match");
|
|
536
|
+
assert.ok(!taskManager1EventFired, "Should only fire completed event once on taskManager1");
|
|
537
|
+
taskManager1EventFired = true;
|
|
538
|
+
});
|
|
539
|
+
taskManager1.complete(taskId);
|
|
540
|
+
containerRuntimeFactory.processAllMessages();
|
|
541
|
+
assert.ok(taskManager1EventFired, "Should have raised completed event on taskManager1");
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
describe("Behavior after attaching", () => {
|
|
546
|
+
describe("Volunteering for a task", () => {
|
|
547
|
+
it("Will keep task assignment after attaching if clientId is defined", async () => {
|
|
548
|
+
const taskId = "taskId";
|
|
549
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
550
|
+
containerRuntimeFactory.processAllMessages();
|
|
551
|
+
await volunteerTaskP;
|
|
552
|
+
await attachTaskManager1();
|
|
553
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
554
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
555
|
+
});
|
|
556
|
+
it("Will lose task assignment after attaching if clientId is undefined", async () => {
|
|
557
|
+
taskManager1.runtime.clientId = undefined;
|
|
558
|
+
const taskId = "taskId";
|
|
559
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
560
|
+
containerRuntimeFactory.processAllMessages();
|
|
561
|
+
await volunteerTaskP;
|
|
562
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
563
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
564
|
+
assert.strictEqual(taskManager1.taskQueues.get(taskId)?.[0], placeholderClientId, "taskQueue should have placeholder clientId");
|
|
565
|
+
let taskManager1EventFired = false;
|
|
566
|
+
taskManager1.on("lost", (completedTaskId) => {
|
|
567
|
+
assert.ok(completedTaskId === taskId, "taskId should match");
|
|
568
|
+
assert.ok(!taskManager1EventFired, "Should only fire lost event once on taskManager1");
|
|
569
|
+
taskManager1EventFired = true;
|
|
570
|
+
});
|
|
571
|
+
await attachTaskManager1();
|
|
572
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
573
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
574
|
+
assert.ok(taskManager1EventFired, "Should have raised lost event on taskManager1");
|
|
575
|
+
assert.ok(taskManager1.taskQueues.size === 0, "taskQueue should be empty");
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
describe("Subscribing to a task", () => {
|
|
579
|
+
it("Can subscribe to a task and stay assigned/subscribed after attach", async () => {
|
|
580
|
+
const taskId = "taskId";
|
|
581
|
+
taskManager1.subscribeToTask(taskId);
|
|
582
|
+
containerRuntimeFactory.processAllMessages();
|
|
583
|
+
await attachTaskManager1();
|
|
584
|
+
containerRuntimeFactory.processAllMessages();
|
|
585
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
586
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
587
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
588
|
+
});
|
|
589
|
+
it("Can subscribe to a task and stay subscribed after attach if clientId was undefined", async () => {
|
|
590
|
+
taskManager1.runtime.clientId = undefined;
|
|
591
|
+
const taskId = "taskId";
|
|
592
|
+
taskManager1.subscribeToTask(taskId);
|
|
593
|
+
containerRuntimeFactory.processAllMessages();
|
|
594
|
+
let taskManager1EventFired = false;
|
|
595
|
+
taskManager1.on("lost", (completedTaskId) => {
|
|
596
|
+
assert.ok(completedTaskId === taskId, "taskId should match");
|
|
597
|
+
assert.ok(!taskManager1EventFired, "Should only fire lost event once on taskManager1");
|
|
598
|
+
taskManager1EventFired = true;
|
|
599
|
+
});
|
|
600
|
+
await attachTaskManager1();
|
|
601
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
602
|
+
containerRuntimeFactory.processAllMessages();
|
|
603
|
+
assert.ok(taskManager1EventFired, "Should have raised lost event on taskManager1");
|
|
604
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
605
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
606
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
607
|
+
assert.ok(taskManager1.taskQueues.get(taskId)?.length !== 0, "taskQueue should not be empty");
|
|
608
|
+
assert.notStrictEqual(taskManager1.taskQueues.get(taskId)?.[0], placeholderClientId, "taskQueue should not have placeholder clientId");
|
|
609
|
+
});
|
|
610
|
+
// todo AB#7310
|
|
611
|
+
it.skip("Can abandon a subscribed task after attach", async () => {
|
|
612
|
+
const taskId = "taskId";
|
|
613
|
+
taskManager1.subscribeToTask(taskId);
|
|
614
|
+
containerRuntimeFactory.processAllMessages();
|
|
615
|
+
await attachTaskManager1();
|
|
616
|
+
taskManager1.abandon(taskId);
|
|
617
|
+
containerRuntimeFactory.processAllMessages();
|
|
618
|
+
assert.ok(!taskManager1.queued(taskId), "Task manager 1 should not be queued");
|
|
619
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
620
|
+
assert.ok(!taskManager1.subscribed(taskId), "Task manager 1 should not be subscribed");
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
describe("Completing tasks", () => {
|
|
624
|
+
it("Can complete a task after attach", async () => {
|
|
625
|
+
const taskId = "taskId";
|
|
626
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
627
|
+
containerRuntimeFactory.processAllMessages();
|
|
628
|
+
await volunteerTaskP;
|
|
629
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
630
|
+
await attachTaskManager1();
|
|
631
|
+
taskManager1.complete(taskId);
|
|
632
|
+
containerRuntimeFactory.processAllMessages();
|
|
633
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
634
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
635
|
+
});
|
|
636
|
+
it("Can emit completed event after attach", async () => {
|
|
637
|
+
const taskId = "taskId";
|
|
638
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
639
|
+
containerRuntimeFactory.processAllMessages();
|
|
640
|
+
await volunteerTaskP1;
|
|
641
|
+
await attachTaskManager1();
|
|
642
|
+
let taskManager1EventFired = false;
|
|
643
|
+
taskManager1.on("completed", (completedTaskId) => {
|
|
644
|
+
assert.ok(completedTaskId === taskId, "taskId should match");
|
|
645
|
+
assert.ok(!taskManager1EventFired, "Should only fire completed event once on taskManager1");
|
|
646
|
+
taskManager1EventFired = true;
|
|
647
|
+
});
|
|
648
|
+
taskManager1.complete(taskId);
|
|
649
|
+
containerRuntimeFactory.processAllMessages();
|
|
650
|
+
assert.ok(taskManager1EventFired, "Should have raised completed event on taskManager1");
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
describe("Disconnect/Reconnect", () => {
|
|
656
|
+
let containerRuntimeFactory;
|
|
657
|
+
let containerRuntime1;
|
|
658
|
+
let containerRuntime2;
|
|
659
|
+
let taskManager1;
|
|
660
|
+
let taskManager2;
|
|
661
|
+
beforeEach(async () => {
|
|
662
|
+
containerRuntimeFactory = new MockContainerRuntimeFactoryForReconnection();
|
|
663
|
+
// Create the first TaskManager.
|
|
664
|
+
const dataStoreRuntime1 = new MockFluidDataStoreRuntime();
|
|
665
|
+
containerRuntime1 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime1);
|
|
666
|
+
const services1 = {
|
|
667
|
+
deltaConnection: dataStoreRuntime1.createDeltaConnection(),
|
|
668
|
+
objectStorage: new MockStorage(),
|
|
669
|
+
};
|
|
670
|
+
taskManager1 = new TaskManager("task-manager-1", dataStoreRuntime1, TaskManagerFactory.Attributes);
|
|
671
|
+
taskManager1.connect(services1);
|
|
672
|
+
// Create the second TaskManager.
|
|
673
|
+
const dataStoreRuntime2 = new MockFluidDataStoreRuntime();
|
|
674
|
+
containerRuntime2 = containerRuntimeFactory.createContainerRuntime(dataStoreRuntime2);
|
|
675
|
+
const services2 = {
|
|
676
|
+
deltaConnection: dataStoreRuntime2.createDeltaConnection(),
|
|
677
|
+
objectStorage: new MockStorage(),
|
|
678
|
+
};
|
|
679
|
+
taskManager2 = new TaskManager("task-manager-2", dataStoreRuntime2, TaskManagerFactory.Attributes);
|
|
680
|
+
taskManager2.connect(services2);
|
|
681
|
+
});
|
|
682
|
+
it("Can create a TaskManager while disconnected", () => {
|
|
683
|
+
containerRuntime1.connected = false;
|
|
684
|
+
assert.ok(taskManager1, "Could not create a task manager");
|
|
685
|
+
assert.ok(taskManager1.isAttached(), "TaskManager should be attached");
|
|
686
|
+
assert.ok(!taskManager1.connected, "TaskManager should be disconnected");
|
|
687
|
+
});
|
|
688
|
+
describe("Behavior transitioning to disconnect", () => {
|
|
689
|
+
describe("Volunteering for a task", () => {
|
|
690
|
+
it("Disconnect while assigned: Raises a lost event and loses the task assignment", async () => {
|
|
691
|
+
const taskId = "taskId";
|
|
692
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
693
|
+
containerRuntimeFactory.processAllMessages();
|
|
694
|
+
const isAssigned = await volunteerTaskP;
|
|
695
|
+
assert.ok(isAssigned, "Should resolve true");
|
|
696
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
697
|
+
let lostRaised = false;
|
|
698
|
+
taskManager1.once("lost", () => {
|
|
699
|
+
lostRaised = true;
|
|
700
|
+
});
|
|
701
|
+
containerRuntime1.connected = false;
|
|
702
|
+
containerRuntimeFactory.processAllMessages();
|
|
703
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
704
|
+
assert.ok(lostRaised, "Should have raised a lost event");
|
|
705
|
+
});
|
|
706
|
+
it("Disconnect while queued: Rejects the volunteerForTask promise and exits the queue", async () => {
|
|
707
|
+
const taskId = "taskId";
|
|
708
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
709
|
+
const volunteerTaskP2 = taskManager2.volunteerForTask(taskId);
|
|
710
|
+
containerRuntimeFactory.processAllMessages();
|
|
711
|
+
const isAssigned = await volunteerTaskP1;
|
|
712
|
+
assert.ok(isAssigned, "Should resolve true");
|
|
713
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 Should be assigned");
|
|
714
|
+
assert.ok(taskManager2.queued(taskId), "Task manager 2 should be queued");
|
|
715
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
716
|
+
containerRuntime2.connected = false;
|
|
717
|
+
containerRuntimeFactory.processAllMessages();
|
|
718
|
+
await assert.rejects(volunteerTaskP2, "Should have rejected the P2 promise");
|
|
719
|
+
assert.ok(!taskManager2.queued(taskId), "Task manager 2 should not be queued");
|
|
720
|
+
assert.ok(!taskManager2.assigned(taskId), "Task manager 2 should not be assigned");
|
|
721
|
+
});
|
|
722
|
+
it("Disconnect while queued: Removed from the queue for other clients", async () => {
|
|
723
|
+
const taskId = "taskId";
|
|
724
|
+
const volunteerTaskP1 = taskManager1.volunteerForTask(taskId);
|
|
725
|
+
taskManager2.subscribeToTask(taskId);
|
|
726
|
+
containerRuntimeFactory.processAllMessages();
|
|
727
|
+
await volunteerTaskP1;
|
|
728
|
+
const clientId1 = containerRuntime1.clientId;
|
|
729
|
+
const clientId2 = containerRuntime2.clientId;
|
|
730
|
+
assert.deepEqual(taskManager1.taskQueues.get(taskId), [clientId1, clientId2], "Task queue should have both clients");
|
|
731
|
+
containerRuntime2.connected = false;
|
|
732
|
+
containerRuntimeFactory.processAllMessages();
|
|
733
|
+
assert.deepEqual(taskManager1.taskQueues.get(taskId), [clientId1], "Task queue should only have client 1");
|
|
734
|
+
});
|
|
735
|
+
it("Disconnect while pending: Rejects the volunteerForTask promise", async () => {
|
|
736
|
+
const taskId = "taskId";
|
|
737
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
738
|
+
containerRuntime1.connected = false;
|
|
739
|
+
containerRuntimeFactory.processAllMessages();
|
|
740
|
+
await assert.rejects(volunteerTaskP, "Should have rejected the promise");
|
|
741
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
742
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
describe("Subscribing to a task", () => { });
|
|
746
|
+
describe("Completing tasks", () => { });
|
|
747
|
+
});
|
|
748
|
+
describe("Behavior while disconnected", () => {
|
|
749
|
+
describe("Volunteering for a task", () => {
|
|
750
|
+
it("Immediately rejects attempts to lock task and throws on abandon", async () => {
|
|
751
|
+
const taskId = "taskId";
|
|
752
|
+
containerRuntime1.connected = false;
|
|
753
|
+
containerRuntimeFactory.processAllMessages();
|
|
754
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
755
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
756
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
757
|
+
await assert.rejects(volunteerTaskP);
|
|
758
|
+
containerRuntimeFactory.processAllMessages();
|
|
759
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
760
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
describe("Subscribing to a task", () => {
|
|
764
|
+
it("Can subscribe while disconnected", async () => {
|
|
765
|
+
const taskId = "taskId";
|
|
766
|
+
containerRuntime1.connected = false;
|
|
767
|
+
containerRuntimeFactory.processAllMessages();
|
|
768
|
+
taskManager1.subscribeToTask(taskId);
|
|
769
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
770
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
771
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
772
|
+
containerRuntimeFactory.processAllMessages();
|
|
773
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
774
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
775
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
776
|
+
containerRuntime1.connected = true;
|
|
777
|
+
containerRuntimeFactory.processAllMessages();
|
|
778
|
+
assert.ok(taskManager1.queued(taskId), "Should be queued");
|
|
779
|
+
assert.ok(taskManager1.assigned(taskId), "Should be assigned");
|
|
780
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
781
|
+
});
|
|
782
|
+
it("Can abandon subscription while disconnected", async () => {
|
|
783
|
+
const taskId = "taskId";
|
|
784
|
+
containerRuntime1.connected = false;
|
|
785
|
+
containerRuntimeFactory.processAllMessages();
|
|
786
|
+
taskManager1.subscribeToTask(taskId);
|
|
787
|
+
taskManager1.abandon(taskId);
|
|
788
|
+
containerRuntimeFactory.processAllMessages();
|
|
789
|
+
containerRuntime1.connected = true;
|
|
790
|
+
containerRuntimeFactory.processAllMessages();
|
|
791
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
792
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
793
|
+
assert.ok(!taskManager1.subscribed(taskId), "Task manager 1 should not be subscribed");
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
describe("Completing tasks", () => {
|
|
797
|
+
it("Immediately throws on attempt to complete task", async () => {
|
|
798
|
+
const taskId = "taskId";
|
|
799
|
+
containerRuntime1.connected = false;
|
|
800
|
+
containerRuntimeFactory.processAllMessages();
|
|
801
|
+
assert.throws(() => {
|
|
802
|
+
taskManager1.complete(taskId);
|
|
803
|
+
}, "Should throw error");
|
|
804
|
+
containerRuntimeFactory.processAllMessages();
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
describe("Behavior transitioning to connected", () => {
|
|
809
|
+
describe("Volunteering for a task", () => {
|
|
810
|
+
it("Does not re-attempt to enter the queue for un-ack'd ops", async () => {
|
|
811
|
+
const taskId = "taskId";
|
|
812
|
+
const volunteerTaskP = taskManager1.volunteerForTask(taskId);
|
|
813
|
+
containerRuntime1.connected = false;
|
|
814
|
+
containerRuntimeFactory.processAllMessages();
|
|
815
|
+
await assert.rejects(volunteerTaskP, "Should have rejected the promise");
|
|
816
|
+
containerRuntime1.connected = true;
|
|
817
|
+
containerRuntimeFactory.processAllMessages();
|
|
818
|
+
assert.ok(!taskManager1.queued(taskId), "Should not be queued");
|
|
819
|
+
assert.ok(!taskManager1.assigned(taskId), "Should not be assigned");
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
describe("Subscribing to a task", () => {
|
|
823
|
+
it("Does re-attempt to enter the queue when subscribed", async () => {
|
|
824
|
+
const taskId = "taskId";
|
|
825
|
+
taskManager1.subscribeToTask(taskId);
|
|
826
|
+
containerRuntimeFactory.processAllMessages();
|
|
827
|
+
containerRuntime1.connected = false;
|
|
828
|
+
assert.ok(!taskManager1.queued(taskId), "Task manager 1 should not be queued");
|
|
829
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
830
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
831
|
+
containerRuntime1.connected = true;
|
|
832
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
833
|
+
assert.ok(!taskManager1.assigned(taskId), "Task manager 1 should not be assigned");
|
|
834
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
835
|
+
containerRuntimeFactory.processAllMessages();
|
|
836
|
+
assert.ok(taskManager1.queued(taskId), "Task manager 1 should be queued");
|
|
837
|
+
assert.ok(taskManager1.assigned(taskId), "Task manager 1 should be assigned");
|
|
838
|
+
assert.ok(taskManager1.subscribed(taskId), "Task manager 1 should be subscribed");
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
describe("Completing tasks", () => { });
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
//# sourceMappingURL=taskManager.spec.js.map
|