Skip to content

Commit 3956fbc

Browse files
chore: more reliable SessionExportManager tests
1 parent eb055eb commit 3956fbc

File tree

2 files changed

+57
-47
lines changed

2 files changed

+57
-47
lines changed

src/common/sessionExportsManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ export class SessionExportsManager extends EventEmitter<SessionExportsManagerEve
209209
delete this.sessionExports[inProgressExport.exportName];
210210
throw pipelineError;
211211
} finally {
212-
void input.close();
213212
if (pipeSuccessful) {
214213
this.sessionExports[inProgressExport.exportName] = {
215214
...inProgressExport,
@@ -218,6 +217,7 @@ export class SessionExportsManager extends EventEmitter<SessionExportsManagerEve
218217
};
219218
this.emit("export-available", inProgressExport.exportURI);
220219
}
220+
void input.close();
221221
}
222222
} catch (error) {
223223
this.logger.error({

tests/unit/common/sessionExportsManager.test.ts

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ function getExportNameAndPath(sessionId: string, timestamp: number) {
3737
};
3838
}
3939

40-
function createDummyFindCursor(dataArray: unknown[], chunkPushTimeoutMs?: number): FindCursor {
40+
function createDummyFindCursor(
41+
dataArray: unknown[],
42+
chunkPushTimeoutMs?: number
43+
): { cursor: FindCursor; cursorCloseNotification: Promise<void> } {
4144
let index = 0;
4245
const readable = new Readable({
4346
objectMode: true,
@@ -56,14 +59,26 @@ function createDummyFindCursor(dataArray: unknown[], chunkPushTimeoutMs?: number
5659
},
5760
});
5861

62+
let notifyClose: () => Promise<void>;
63+
const cursorCloseNotification = new Promise<void>((resolve) => {
64+
notifyClose = async () => {
65+
await timeout(10);
66+
resolve();
67+
};
68+
});
69+
readable.once("close", () => void notifyClose?.());
70+
5971
return {
60-
stream() {
61-
return readable;
62-
},
63-
close() {
64-
return Promise.resolve(readable.destroy());
65-
},
66-
} as unknown as FindCursor;
72+
cursor: {
73+
stream() {
74+
return readable;
75+
},
76+
close() {
77+
return Promise.resolve(readable.destroy());
78+
},
79+
} as unknown as FindCursor,
80+
cursorCloseNotification,
81+
};
6782
}
6883

6984
async function fileExists(filePath: string) {
@@ -92,21 +107,22 @@ describe("SessionExportsManager unit test", () => {
92107
// This export will finish in at-least 1 second
93108
const { exportName: exportName1 } = getExportNameAndPath(session.sessionId, Date.now());
94109
manager.createJSONExport({
95-
input: createDummyFindCursor([{ name: "Test1" }], 1000),
110+
input: createDummyFindCursor([{ name: "Test1" }], 1000).cursor,
96111
exportName: exportName1,
97112
jsonExportFormat: "relaxed",
98113
});
99114

100115
// This export will finish way sooner than the first one
101116
const { exportName: exportName2 } = getExportNameAndPath(session.sessionId, Date.now());
117+
const { cursor, cursorCloseNotification } = createDummyFindCursor([{ name: "Test1" }]);
102118
manager.createJSONExport({
103-
input: createDummyFindCursor([{ name: "Test1" }]),
119+
input: cursor,
104120
exportName: exportName2,
105121
jsonExportFormat: "relaxed",
106122
});
107123

108124
// Small timeout to let the second export finish
109-
await timeout(10);
125+
await cursorCloseNotification;
110126
expect(manager.availableExports).toHaveLength(1);
111127
expect(manager.availableExports[0]?.exportName).toEqual(exportName2);
112128
});
@@ -119,42 +135,40 @@ describe("SessionExportsManager unit test", () => {
119135

120136
it("should throw if the resource is still being generated", async () => {
121137
const { exportName } = getExportNameAndPath(session.sessionId, Date.now());
122-
const inputCursor = createDummyFindCursor([{ name: "Test1" }], 100);
138+
const { cursor } = createDummyFindCursor([{ name: "Test1" }], 100);
123139
manager.createJSONExport({
124-
input: inputCursor,
140+
input: cursor,
125141
exportName,
126142
jsonExportFormat: "relaxed",
127143
});
128-
// Small timeout but it won't be sufficient and the export
129-
// generation will still be in progress
130-
await timeout(10);
144+
// note that we do not wait for cursor close
131145
await expect(() => manager.readExport(exportName)).rejects.toThrow(
132146
"Requested export is still being generated!"
133147
);
134148
});
135149

136150
it("should return the resource content if the resource is ready to be consumed", async () => {
137151
const { exportName } = getExportNameAndPath(session.sessionId, Date.now());
138-
const inputCursor = createDummyFindCursor([]);
152+
const { cursor, cursorCloseNotification } = createDummyFindCursor([]);
139153
manager.createJSONExport({
140-
input: inputCursor,
154+
input: cursor,
141155
exportName,
142156
jsonExportFormat: "relaxed",
143157
});
144-
// Small timeout to account for async operation
145-
await timeout(10);
158+
await cursorCloseNotification;
146159
expect(await manager.readExport(exportName)).toEqual("[]");
147160
});
148161
});
149162

150163
describe("#createJSONExport", () => {
151-
let inputCursor: FindCursor;
164+
let cursor: FindCursor;
165+
let cursorCloseNotification: Promise<void>;
152166
let exportName: string;
153167
let exportPath: string;
154168
let exportURI: string;
155169
beforeEach(() => {
156-
void inputCursor?.close();
157-
inputCursor = createDummyFindCursor([
170+
void cursor?.close();
171+
({ cursor, cursorCloseNotification } = createDummyFindCursor([
158172
{
159173
name: "foo",
160174
longNumber: Long.fromNumber(12),
@@ -163,22 +177,21 @@ describe("SessionExportsManager unit test", () => {
163177
name: "bar",
164178
longNumber: Long.fromNumber(123456),
165179
},
166-
]);
180+
]));
167181
({ exportName, exportPath, exportURI } = getExportNameAndPath(session.sessionId, Date.now()));
168182
});
169183

170184
describe("when cursor is empty", () => {
171185
it("should create an empty export", async () => {
172-
inputCursor = createDummyFindCursor([]);
186+
const { cursor, cursorCloseNotification } = createDummyFindCursor([]);
173187

174188
const emitSpy = vi.spyOn(manager, "emit");
175189
manager.createJSONExport({
176-
input: inputCursor,
190+
input: cursor,
177191
exportName,
178192
jsonExportFormat: "relaxed",
179193
});
180-
// Small timeout to account for async operation
181-
await timeout(10);
194+
await cursorCloseNotification;
182195

183196
// Updates available export
184197
const availableExports = manager.availableExports;
@@ -206,12 +219,11 @@ describe("SessionExportsManager unit test", () => {
206219
it("should export relaxed json, update available exports and emit export-available event", async () => {
207220
const emitSpy = vi.spyOn(manager, "emit");
208221
manager.createJSONExport({
209-
input: inputCursor,
222+
input: cursor,
210223
exportName,
211224
jsonExportFormat: "relaxed",
212225
});
213-
// Small timeout to account for async operation
214-
await timeout(10);
226+
await cursorCloseNotification;
215227

216228
const expectedExportName = exportName.endsWith(".json") ? exportName : `${exportName}.json`;
217229
// Updates available export
@@ -241,12 +253,11 @@ describe("SessionExportsManager unit test", () => {
241253
it("should export canonical json, update available exports and emit export-available event", async () => {
242254
const emitSpy = vi.spyOn(manager, "emit");
243255
manager.createJSONExport({
244-
input: inputCursor,
256+
input: cursor,
245257
exportName,
246258
jsonExportFormat: "canonical",
247259
});
248-
// Small timeout to account for async operation
249-
await timeout(50);
260+
await cursorCloseNotification;
250261

251262
const expectedExportName = exportName.endsWith(".json") ? exportName : `${exportName}.json`;
252263
// Updates available export
@@ -302,12 +313,11 @@ describe("SessionExportsManager unit test", () => {
302313
});
303314
};
304315
manager.createJSONExport({
305-
input: inputCursor,
316+
input: cursor,
306317
exportName,
307318
jsonExportFormat: "relaxed",
308319
});
309-
// Small timeout to account for async operation
310-
await timeout(50);
320+
await cursorCloseNotification;
311321

312322
// Because the export was never populated in the available exports.
313323
await expect(() => manager.readExport(exportName)).rejects.toThrow(
@@ -321,10 +331,11 @@ describe("SessionExportsManager unit test", () => {
321331
});
322332

323333
describe("#cleanupExpiredExports", () => {
324-
let input: FindCursor;
334+
let cursor: FindCursor;
335+
let cursorCloseNotification: Promise<void>;
325336
beforeEach(() => {
326-
void input?.close();
327-
input = createDummyFindCursor([
337+
void cursor?.close();
338+
({ cursor, cursorCloseNotification } = createDummyFindCursor([
328339
{
329340
name: "foo",
330341
longNumber: Long.fromNumber(12),
@@ -333,7 +344,7 @@ describe("SessionExportsManager unit test", () => {
333344
name: "bar",
334345
longNumber: Long.fromNumber(123456),
335346
},
336-
]);
347+
]));
337348
});
338349

339350
it("should not clean up in-progress exports", async () => {
@@ -347,8 +358,9 @@ describe("SessionExportsManager unit test", () => {
347358
},
348359
new CompositeLogger()
349360
);
361+
const { cursor } = createDummyFindCursor([{ name: "Test" }], 2000);
350362
manager.createJSONExport({
351-
input: createDummyFindCursor([{ name: "Test" }], 2000),
363+
input: cursor,
352364
exportName,
353365
jsonExportFormat: "relaxed",
354366
});
@@ -374,13 +386,11 @@ describe("SessionExportsManager unit test", () => {
374386
new CompositeLogger()
375387
);
376388
manager.createJSONExport({
377-
input,
389+
input: cursor,
378390
exportName,
379391
jsonExportFormat: "relaxed",
380392
});
381-
// Small timeout to account for async operation and let export
382-
// finish
383-
await timeout(10);
393+
await cursorCloseNotification;
384394

385395
expect(manager.availableExports).toContainEqual(
386396
expect.objectContaining({

0 commit comments

Comments
 (0)