UNPKG

26.5 kBJavaScriptView Raw
1"use strict";
2// Copyright (c) Jupyter Development Team.
3// Distributed under the terms of the Modified BSD License.
4Object.defineProperty(exports, "__esModule", { value: true });
5exports.FakeUserManager = exports.changeKernel = exports.MockShellFuture = exports.ServiceManagerMock = exports.KernelSpecManagerMock = exports.SessionManagerMock = exports.ContentsManagerMock = exports.SessionConnectionMock = exports.KernelMock = exports.cloneKernel = exports.NOTEBOOK_PATHS = exports.KERNEL_MODELS = exports.KERNELSPECS = exports.DEFAULT_NAME = void 0;
6// We explicitly reference the jest typings since the jest.d.ts file shipped
7// with jest 26 masks the @types/jest typings
8/// <reference types="jest" />
9const coreutils_1 = require("@jupyterlab/coreutils");
10const coreutils_2 = require("@lumino/coreutils");
11const properties_1 = require("@lumino/properties");
12const signaling_1 = require("@lumino/signaling");
13const basemanager_1 = require("./basemanager");
14const contents_1 = require("./contents");
15const kernel_1 = require("./kernel");
16const serverconnection_1 = require("./serverconnection");
17// The default kernel name
18exports.DEFAULT_NAME = 'python3';
19exports.KERNELSPECS = {
20 [exports.DEFAULT_NAME]: {
21 argv: [
22 '/Users/someuser/miniconda3/envs/jupyterlab/bin/python',
23 '-m',
24 'ipykernel_launcher',
25 '-f',
26 '{connection_file}'
27 ],
28 display_name: 'Python 3',
29 language: 'python',
30 metadata: {},
31 name: exports.DEFAULT_NAME,
32 resources: {}
33 },
34 irkernel: {
35 argv: [
36 '/Users/someuser/miniconda3/envs/jupyterlab/bin/python',
37 '-m',
38 'ipykernel_launcher',
39 '-f',
40 '{connection_file}'
41 ],
42 display_name: 'R',
43 language: 'r',
44 metadata: {},
45 name: 'irkernel',
46 resources: {}
47 }
48};
49exports.KERNEL_MODELS = [
50 {
51 name: exports.DEFAULT_NAME,
52 id: coreutils_2.UUID.uuid4()
53 },
54 {
55 name: 'r',
56 id: coreutils_2.UUID.uuid4()
57 },
58 {
59 name: exports.DEFAULT_NAME,
60 id: coreutils_2.UUID.uuid4()
61 }
62];
63// Notebook Paths for certain kernel name
64exports.NOTEBOOK_PATHS = {
65 python3: ['Untitled.ipynb', 'Untitled1.ipynb', 'Untitled2.ipynb'],
66 r: ['Visualization.ipynb', 'Analysis.ipynb', 'Conclusion.ipynb']
67};
68/**
69 * Clone a kernel connection.
70 */
71function cloneKernel(kernel) {
72 return kernel.clone();
73}
74exports.cloneKernel = cloneKernel;
75/**
76 * A mock kernel object.
77 *
78 * @param model The model of the kernel
79 */
80exports.KernelMock = jest.fn(options => {
81 const model = { id: 'foo', name: exports.DEFAULT_NAME, ...options.model };
82 options = {
83 clientId: coreutils_2.UUID.uuid4(),
84 username: coreutils_2.UUID.uuid4(),
85 ...options,
86 model
87 };
88 let executionCount = 0;
89 const spec = Private.kernelSpecForKernelName(model.name);
90 const thisObject = {
91 ...jest.requireActual('@jupyterlab/services'),
92 ...options,
93 ...model,
94 model,
95 serverSettings: serverconnection_1.ServerConnection.makeSettings(options.serverSettings),
96 status: 'idle',
97 spec: Promise.resolve(spec),
98 dispose: jest.fn(),
99 clone: jest.fn(() => {
100 const newKernel = Private.cloneKernel(options);
101 newKernel.iopubMessage.connect((_, args) => {
102 iopubMessageSignal.emit(args);
103 });
104 newKernel.statusChanged.connect((_, args) => {
105 thisObject.status = args;
106 statusChangedSignal.emit(args);
107 });
108 return newKernel;
109 }),
110 info: Promise.resolve(Private.getInfo(model.name)),
111 shutdown: jest.fn(() => Promise.resolve(void 0)),
112 requestHistory: jest.fn(() => {
113 const historyReply = kernel_1.KernelMessage.createMessage({
114 channel: 'shell',
115 msgType: 'history_reply',
116 session: options.clientId,
117 username: options.username,
118 content: {
119 history: [],
120 status: 'ok'
121 }
122 });
123 return Promise.resolve(historyReply);
124 }),
125 restart: jest.fn(() => Promise.resolve(void 0)),
126 requestExecute: jest.fn(options => {
127 const msgId = coreutils_2.UUID.uuid4();
128 executionCount++;
129 Private.lastMessageProperty.set(thisObject, msgId);
130 const msg = kernel_1.KernelMessage.createMessage({
131 channel: 'iopub',
132 msgType: 'execute_input',
133 session: thisObject.clientId,
134 username: thisObject.username,
135 msgId,
136 content: {
137 code: options.code,
138 execution_count: executionCount
139 }
140 });
141 iopubMessageSignal.emit(msg);
142 const reply = kernel_1.KernelMessage.createMessage({
143 channel: 'shell',
144 msgType: 'execute_reply',
145 session: thisObject.clientId,
146 username: thisObject.username,
147 msgId,
148 content: {
149 user_expressions: {},
150 execution_count: executionCount,
151 status: 'ok'
152 }
153 });
154 return new exports.MockShellFuture(reply);
155 })
156 };
157 // Add signals.
158 const iopubMessageSignal = new signaling_1.Signal(thisObject);
159 const statusChangedSignal = new signaling_1.Signal(thisObject);
160 const pendingInputSignal = new signaling_1.Signal(thisObject);
161 thisObject.statusChanged = statusChangedSignal;
162 thisObject.iopubMessage = iopubMessageSignal;
163 thisObject.pendingInput = pendingInputSignal;
164 thisObject.hasPendingInput = false;
165 return thisObject;
166});
167/**
168 * A mock session connection.
169 *
170 * @param options Addition session options to use
171 * @param model A session model to use
172 */
173exports.SessionConnectionMock = jest.fn((options, kernel) => {
174 var _a, _b;
175 const name = (kernel === null || kernel === void 0 ? void 0 : kernel.name) || ((_b = (_a = options.model) === null || _a === void 0 ? void 0 : _a.kernel) === null || _b === void 0 ? void 0 : _b.name) || exports.DEFAULT_NAME;
176 kernel = kernel || new exports.KernelMock({ model: { name } });
177 const model = {
178 id: coreutils_2.UUID.uuid4(),
179 path: 'foo',
180 type: 'notebook',
181 name: 'foo',
182 ...options.model,
183 kernel: kernel.model
184 };
185 const thisObject = {
186 ...jest.requireActual('@jupyterlab/services'),
187 ...options,
188 model,
189 ...model,
190 kernel,
191 serverSettings: serverconnection_1.ServerConnection.makeSettings(options.serverSettings),
192 dispose: jest.fn(),
193 changeKernel: jest.fn(partialModel => {
194 return changeKernel(kernel, partialModel);
195 }),
196 shutdown: jest.fn(() => Promise.resolve(void 0)),
197 setPath: jest.fn(path => {
198 thisObject.path = path;
199 propertyChangedSignal.emit('path');
200 return Promise.resolve();
201 }),
202 setName: jest.fn(name => {
203 thisObject.name = name;
204 propertyChangedSignal.emit('name');
205 return Promise.resolve();
206 }),
207 setType: jest.fn(type => {
208 thisObject.type = type;
209 propertyChangedSignal.emit('type');
210 return Promise.resolve();
211 })
212 };
213 const disposedSignal = new signaling_1.Signal(thisObject);
214 const propertyChangedSignal = new signaling_1.Signal(thisObject);
215 const statusChangedSignal = new signaling_1.Signal(thisObject);
216 const connectionStatusChangedSignal = new signaling_1.Signal(thisObject);
217 const kernelChangedSignal = new signaling_1.Signal(thisObject);
218 const iopubMessageSignal = new signaling_1.Signal(thisObject);
219 const unhandledMessageSignal = new signaling_1.Signal(thisObject);
220 const pendingInputSignal = new signaling_1.Signal(thisObject);
221 kernel.iopubMessage.connect((_, args) => {
222 iopubMessageSignal.emit(args);
223 }, thisObject);
224 kernel.statusChanged.connect((_, args) => {
225 statusChangedSignal.emit(args);
226 }, thisObject);
227 kernel.pendingInput.connect((_, args) => {
228 pendingInputSignal.emit(args);
229 }, thisObject);
230 thisObject.disposed = disposedSignal;
231 thisObject.connectionStatusChanged = connectionStatusChangedSignal;
232 thisObject.propertyChanged = propertyChangedSignal;
233 thisObject.statusChanged = statusChangedSignal;
234 thisObject.kernelChanged = kernelChangedSignal;
235 thisObject.iopubMessage = iopubMessageSignal;
236 thisObject.unhandledMessage = unhandledMessageSignal;
237 thisObject.pendingInput = pendingInputSignal;
238 return thisObject;
239});
240/**
241 * A mock contents manager.
242 */
243exports.ContentsManagerMock = jest.fn(() => {
244 const files = new Map();
245 const dummy = new contents_1.ContentsManager();
246 const checkpoints = new Map();
247 const checkPointContent = new Map();
248 const baseModel = Private.createFile({ type: 'directory' });
249 // create the default drive
250 files.set('', new Map([
251 ['', { ...baseModel, path: '', name: '' }]
252 ]));
253 const thisObject = {
254 ...jest.requireActual('@jupyterlab/services'),
255 newUntitled: jest.fn(options => {
256 const driveName = dummy.driveName((options === null || options === void 0 ? void 0 : options.path) || '');
257 const localPath = dummy.localPath((options === null || options === void 0 ? void 0 : options.path) || '');
258 // create the test file without the drive name
259 const createOptions = { ...options, path: localPath };
260 const model = Private.createFile(createOptions || {});
261 // re-add the drive name to the model
262 const drivePath = driveName ? `${driveName}:${model.path}` : model.path;
263 const driveModel = {
264 ...model,
265 path: drivePath
266 };
267 files.get(driveName).set(model.path, driveModel);
268 fileChangedSignal.emit({
269 type: 'new',
270 oldValue: null,
271 newValue: driveModel
272 });
273 return Promise.resolve(driveModel);
274 }),
275 createCheckpoint: jest.fn(path => {
276 var _a;
277 const lastModified = new Date().toISOString();
278 const data = { id: coreutils_2.UUID.uuid4(), last_modified: lastModified };
279 checkpoints.set(path, data);
280 const driveName = dummy.driveName(path);
281 const localPath = dummy.localPath(path);
282 checkPointContent.set(path, (_a = files.get(driveName).get(localPath)) === null || _a === void 0 ? void 0 : _a.content);
283 return Promise.resolve(data);
284 }),
285 listCheckpoints: jest.fn(path => {
286 const p = checkpoints.get(path);
287 if (p !== undefined) {
288 return Promise.resolve([p]);
289 }
290 return Promise.resolve([]);
291 }),
292 deleteCheckpoint: jest.fn(path => {
293 if (!checkpoints.has(path)) {
294 return Private.makeResponseError(404);
295 }
296 checkpoints.delete(path);
297 return Promise.resolve();
298 }),
299 restoreCheckpoint: jest.fn(path => {
300 if (!checkpoints.has(path)) {
301 return Private.makeResponseError(404);
302 }
303 const driveName = dummy.driveName(path);
304 const localPath = dummy.localPath(path);
305 files.get(driveName).get(localPath).content =
306 checkPointContent.get(path);
307 return Promise.resolve();
308 }),
309 getSharedModelFactory: jest.fn(() => {
310 return null;
311 }),
312 normalize: jest.fn(path => {
313 return dummy.normalize(path);
314 }),
315 localPath: jest.fn(path => {
316 return dummy.localPath(path);
317 }),
318 resolvePath: jest.fn((root, path) => {
319 return dummy.resolvePath(root, path);
320 }),
321 get: jest.fn((path, options) => {
322 const driveName = dummy.driveName(path);
323 const localPath = dummy.localPath(path);
324 const drive = files.get(driveName);
325 path = Private.fixSlash(localPath);
326 if (!drive.has(path)) {
327 return Private.makeResponseError(404);
328 }
329 const model = drive.get(path);
330 const overrides = {};
331 if (path == 'random-hash.txt') {
332 overrides.hash = Math.random().toString();
333 }
334 else if (path == 'newer-timestamp-no-hash.txt') {
335 overrides.hash = undefined;
336 const tomorrow = new Date();
337 tomorrow.setDate(new Date().getDate() + 1);
338 overrides.last_modified = tomorrow.toISOString();
339 }
340 if (model.type === 'directory') {
341 if ((options === null || options === void 0 ? void 0 : options.content) !== false) {
342 const content = [];
343 drive.forEach(fileModel => {
344 const localPath = dummy.localPath(fileModel.path);
345 if (
346 // If file path is under this directory, add it to contents array.
347 coreutils_1.PathExt.dirname(localPath) == model.path &&
348 // But the directory should exclude itself from the contents array.
349 fileModel !== model) {
350 content.push(fileModel);
351 }
352 });
353 return Promise.resolve({ ...model, content });
354 }
355 return Promise.resolve(model);
356 }
357 if ((options === null || options === void 0 ? void 0 : options.content) != false) {
358 return Promise.resolve(model);
359 }
360 return Promise.resolve({ ...model, content: '', ...overrides });
361 }),
362 driveName: jest.fn(path => {
363 return dummy.driveName(path);
364 }),
365 rename: jest.fn((oldPath, newPath) => {
366 const driveName = dummy.driveName(oldPath);
367 const drive = files.get(driveName);
368 let oldLocalPath = dummy.localPath(oldPath);
369 let newLocalPath = dummy.localPath(newPath);
370 oldLocalPath = Private.fixSlash(oldLocalPath);
371 newLocalPath = Private.fixSlash(newLocalPath);
372 if (!drive.has(oldLocalPath)) {
373 return Private.makeResponseError(404);
374 }
375 const oldValue = drive.get(oldPath);
376 drive.delete(oldPath);
377 const name = coreutils_1.PathExt.basename(newLocalPath);
378 const newValue = { ...oldValue, name, path: newLocalPath };
379 drive.set(newPath, newValue);
380 fileChangedSignal.emit({
381 type: 'rename',
382 oldValue,
383 newValue
384 });
385 return Promise.resolve(newValue);
386 }),
387 delete: jest.fn(path => {
388 const driveName = dummy.driveName(path);
389 const localPath = dummy.localPath(path);
390 const drive = files.get(driveName);
391 path = Private.fixSlash(localPath);
392 if (!drive.has(path)) {
393 return Private.makeResponseError(404);
394 }
395 const oldValue = drive.get(path);
396 drive.delete(path);
397 fileChangedSignal.emit({
398 type: 'delete',
399 oldValue,
400 newValue: null
401 });
402 return Promise.resolve(void 0);
403 }),
404 save: jest.fn((path, options) => {
405 if (path == 'readonly.txt') {
406 return Private.makeResponseError(403);
407 }
408 path = Private.fixSlash(path);
409 const timeStamp = new Date().toISOString();
410 const drive = files.get(dummy.driveName(path));
411 if (drive.has(path)) {
412 const updates = path == 'frozen-time-and-hash.txt'
413 ? {}
414 : {
415 last_modified: timeStamp,
416 hash: timeStamp
417 };
418 drive.set(path, {
419 ...drive.get(path),
420 ...options,
421 ...updates
422 });
423 }
424 else {
425 drive.set(path, {
426 path,
427 name: coreutils_1.PathExt.basename(path),
428 content: '',
429 writable: true,
430 created: timeStamp,
431 type: 'file',
432 format: 'text',
433 mimetype: 'plain/text',
434 ...options,
435 last_modified: timeStamp,
436 hash: timeStamp,
437 hash_algorithm: 'static'
438 });
439 }
440 fileChangedSignal.emit({
441 type: 'save',
442 oldValue: null,
443 newValue: drive.get(path)
444 });
445 return Promise.resolve(drive.get(path));
446 }),
447 getDownloadUrl: jest.fn(path => {
448 return dummy.getDownloadUrl(path);
449 }),
450 addDrive: jest.fn(drive => {
451 dummy.addDrive(drive);
452 files.set(drive.name, new Map([
453 ['', { ...baseModel, path: '', name: '' }]
454 ]));
455 }),
456 dispose: jest.fn()
457 };
458 const fileChangedSignal = new signaling_1.Signal(thisObject);
459 thisObject.fileChanged = fileChangedSignal;
460 return thisObject;
461});
462/**
463 * A mock sessions manager.
464 */
465exports.SessionManagerMock = jest.fn(() => {
466 let sessions = [];
467 const thisObject = {
468 ...jest.requireActual('@jupyterlab/services'),
469 ready: Promise.resolve(void 0),
470 isReady: true,
471 startNew: jest.fn(options => {
472 const session = new exports.SessionConnectionMock({ model: options }, null);
473 sessions.push(session.model);
474 runningChangedSignal.emit(sessions);
475 return Promise.resolve(session);
476 }),
477 connectTo: jest.fn(options => {
478 return new exports.SessionConnectionMock(options, null);
479 }),
480 stopIfNeeded: jest.fn(path => {
481 const length = sessions.length;
482 sessions = sessions.filter(model => model.path !== path);
483 if (sessions.length !== length) {
484 runningChangedSignal.emit(sessions);
485 }
486 return Promise.resolve(void 0);
487 }),
488 refreshRunning: jest.fn(() => Promise.resolve(void 0)),
489 running: jest.fn(() => sessions[Symbol.iterator]())
490 };
491 const runningChangedSignal = new signaling_1.Signal(thisObject);
492 thisObject.runningChanged = runningChangedSignal;
493 return thisObject;
494});
495/**
496 * A mock kernel specs manager
497 */
498exports.KernelSpecManagerMock = jest.fn(() => {
499 const thisObject = {
500 ...jest.requireActual('@jupyterlab/services'),
501 specs: { default: exports.DEFAULT_NAME, kernelspecs: exports.KERNELSPECS },
502 isReady: true,
503 ready: Promise.resolve(void 0),
504 refreshSpecs: jest.fn(() => Promise.resolve(void 0))
505 };
506 return thisObject;
507});
508/**
509 * A mock service manager.
510 */
511exports.ServiceManagerMock = jest.fn(() => {
512 const thisObject = {
513 ...jest.requireActual('@jupyterlab/services'),
514 ready: Promise.resolve(void 0),
515 isReady: true,
516 contents: new exports.ContentsManagerMock(),
517 sessions: new exports.SessionManagerMock(),
518 kernelspecs: new exports.KernelSpecManagerMock(),
519 dispose: jest.fn()
520 };
521 return thisObject;
522});
523/**
524 * A mock kernel shell future.
525 */
526exports.MockShellFuture = jest.fn((result) => {
527 const thisObject = {
528 ...jest.requireActual('@jupyterlab/services'),
529 dispose: jest.fn(),
530 done: Promise.resolve(result)
531 };
532 return thisObject;
533});
534function changeKernel(kernel, partialModel) {
535 if (partialModel.id) {
536 const kernelIdx = exports.KERNEL_MODELS.findIndex(model => {
537 return model.id === partialModel.id;
538 });
539 if (kernelIdx !== -1) {
540 kernel.model = Private.RUNNING_KERNELS[kernelIdx].model;
541 kernel.id = partialModel.id;
542 return Promise.resolve(Private.RUNNING_KERNELS[kernelIdx]);
543 }
544 else {
545 throw new Error(`Unable to change kernel to one with id: ${partialModel.id}`);
546 }
547 }
548 else if (partialModel.name) {
549 const kernelIdx = exports.KERNEL_MODELS.findIndex(model => {
550 return model.name === partialModel.name;
551 });
552 if (kernelIdx !== -1) {
553 kernel.model = Private.RUNNING_KERNELS[kernelIdx].model;
554 kernel.id = partialModel.id;
555 return Promise.resolve(Private.RUNNING_KERNELS[kernelIdx]);
556 }
557 else {
558 throw new Error(`Unable to change kernel to one with name: ${partialModel.name}`);
559 }
560 }
561 else {
562 throw new Error(`Unable to change kernel`);
563 }
564}
565exports.changeKernel = changeKernel;
566/**
567 * A namespace for module private data.
568 */
569var Private;
570(function (Private) {
571 function createFile(options) {
572 options = options || {};
573 let name = coreutils_2.UUID.uuid4();
574 switch (options.type) {
575 case 'directory':
576 name = `Untitled Folder_${name}`;
577 break;
578 case 'notebook':
579 name = `Untitled_${name}.ipynb`;
580 break;
581 default:
582 name = `untitled_${name}${options.ext || '.txt'}`;
583 }
584 const path = coreutils_1.PathExt.join(options.path || '', name);
585 let content = '';
586 if (options.type === 'notebook') {
587 content = JSON.stringify({});
588 }
589 const timeStamp = new Date().toISOString();
590 return {
591 path,
592 content,
593 name,
594 last_modified: timeStamp,
595 writable: true,
596 created: timeStamp,
597 type: options.type || 'file',
598 format: 'text',
599 mimetype: 'plain/text'
600 };
601 }
602 Private.createFile = createFile;
603 function fixSlash(path) {
604 if (path.endsWith('/')) {
605 path = path.slice(0, path.length - 1);
606 }
607 return path;
608 }
609 Private.fixSlash = fixSlash;
610 function makeResponseError(status) {
611 const resp = new Response(void 0, { status });
612 return Promise.reject(new serverconnection_1.ServerConnection.ResponseError(resp));
613 }
614 Private.makeResponseError = makeResponseError;
615 function cloneKernel(options) {
616 return new exports.KernelMock({ ...options, clientId: coreutils_2.UUID.uuid4() });
617 }
618 Private.cloneKernel = cloneKernel;
619 // Get the kernel spec for kernel name
620 function kernelSpecForKernelName(name) {
621 return exports.KERNELSPECS[name];
622 }
623 Private.kernelSpecForKernelName = kernelSpecForKernelName;
624 // Get the kernel info for kernel name
625 function getInfo(name) {
626 return {
627 protocol_version: '1',
628 implementation: 'foo',
629 implementation_version: '1',
630 language_info: {
631 version: '1',
632 name
633 },
634 banner: 'hello, world!',
635 help_links: [],
636 status: 'ok'
637 };
638 }
639 Private.getInfo = getInfo;
640 // This list of running kernels simply mirrors the KERNEL_MODELS and KERNELSPECS lists
641 Private.RUNNING_KERNELS = exports.KERNEL_MODELS.map((model, _) => {
642 return new exports.KernelMock({ model });
643 });
644 Private.lastMessageProperty = new properties_1.AttachedProperty({
645 name: 'lastMessageId',
646 create: () => ''
647 });
648})(Private || (Private = {}));
649/**
650 * The user API service manager.
651 */
652class FakeUserManager extends basemanager_1.BaseManager {
653 /**
654 * Create a new user manager.
655 */
656 constructor(options = {}, identity, permissions) {
657 super(options);
658 this._isReady = false;
659 this._userChanged = new signaling_1.Signal(this);
660 this._connectionFailure = new signaling_1.Signal(this);
661 // Initialize internal data.
662 this._ready = new Promise(resolve => {
663 // Schedule updating the user to the next macro task queue.
664 setTimeout(() => {
665 this._identity = identity;
666 this._permissions = permissions;
667 this._userChanged.emit({
668 identity: this._identity,
669 permissions: this._permissions
670 });
671 resolve();
672 }, 0);
673 })
674 .then(() => {
675 if (this.isDisposed) {
676 return;
677 }
678 this._isReady = true;
679 })
680 .catch(_ => undefined);
681 }
682 /**
683 * Test whether the manager is ready.
684 */
685 get isReady() {
686 return this._isReady;
687 }
688 /**
689 * A promise that fulfills when the manager is ready.
690 */
691 get ready() {
692 return this._ready;
693 }
694 /**
695 * Get the most recently fetched identity.
696 */
697 get identity() {
698 return this._identity;
699 }
700 /**
701 * Get the most recently fetched permissions.
702 */
703 get permissions() {
704 return this._permissions;
705 }
706 /**
707 * A signal emitted when the user changes.
708 */
709 get userChanged() {
710 return this._userChanged;
711 }
712 /**
713 * A signal emitted when there is a connection failure.
714 */
715 get connectionFailure() {
716 return this._connectionFailure;
717 }
718 /**
719 * Dispose of the resources used by the manager.
720 */
721 dispose() {
722 super.dispose();
723 }
724 /**
725 * Force a refresh of the specs from the server.
726 *
727 * @returns A promise that resolves when the specs are fetched.
728 *
729 * #### Notes
730 * This is intended to be called only in response to a user action,
731 * since the manager maintains its internal state.
732 */
733 async refreshUser() {
734 return Promise.resolve();
735 }
736}
737exports.FakeUserManager = FakeUserManager;
738//# sourceMappingURL=testutils.js.map
\No newline at end of file