@sockethub/platform-xmpp 5.0.0-alpha.10 → 5.0.0-alpha.11

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sockethub/platform-xmpp",
3
3
  "description": "A sockethub platform module implementing XMPP functionality",
4
- "version": "5.0.0-alpha.10",
4
+ "version": "5.0.0-alpha.11",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "author": "Nick Jennings <nick@silverbucket.net>",
@@ -42,24 +42,25 @@
42
42
  "scripts": {
43
43
  "build": "bun build src/index.js --outdir dist --target node --format esm --sourcemap=external",
44
44
  "clean": "rm -rf dist",
45
+ "clean:deps": "rm -rf node_modules",
45
46
  "doc": "jsdoc2md --no-gfm --heading-depth 1 src/index.js > API.md"
46
47
  },
47
48
  "dependencies": {
48
49
  "@xmpp/client": "^0.13.6"
49
50
  },
50
51
  "devDependencies": {
51
- "@sockethub/schemas": "^3.0.0-alpha.10",
52
+ "@sockethub/schemas": "^3.0.0-alpha.11",
52
53
  "@xmpp/xml": "^0.13.2",
53
54
  "jsdoc-to-markdown": "^8.0.3",
54
55
  "sinon": "^17.0.2"
55
56
  },
56
57
  "peerDependencies": {
57
- "@sockethub/server": "^5.0.0-alpha.10"
58
+ "@sockethub/server": "^5.0.0-alpha.11"
58
59
  },
59
60
  "peerDependenciesMeta": {
60
61
  "@sockethub/server": {
61
62
  "optional": true
62
63
  }
63
64
  },
64
- "gitHead": "8e1abf116b2a6b57d33c6e1a4af9143870517bae"
65
+ "gitHead": "c243fa9e76c688ce5ffcf524400b1dd27dcce615"
65
66
  }
@@ -63,7 +63,9 @@ export class IncomingHandlers {
63
63
  return;
64
64
  }
65
65
 
66
- this.session.debug("received close event with no handler specified");
66
+ this.session.log.debug(
67
+ "received close event with no handler specified",
68
+ );
67
69
  if (this.session.actor && this.session.sendToClient) {
68
70
  this.session.sendToClient({
69
71
  context: "xmpp",
@@ -71,7 +73,7 @@ export class IncomingHandlers {
71
73
  actor: this.session.actor,
72
74
  target: this.session.actor,
73
75
  });
74
- this.session.debug(
76
+ this.session.log.debug(
75
77
  `**** xmpp this.session.for ${this.session.actor.id} closed`,
76
78
  );
77
79
  }
@@ -95,7 +97,7 @@ export class IncomingHandlers {
95
97
  },
96
98
  });
97
99
  } catch (e) {
98
- this.session.debug("*** XMPP ERROR (rl catch): ", e);
100
+ this.session.log.debug("*** XMPP ERROR (rl catch): ", e);
99
101
  }
100
102
  }
101
103
 
@@ -120,14 +122,14 @@ export class IncomingHandlers {
120
122
  } else {
121
123
  obj.actor.name = stanza.attrs.from.split("/")[1];
122
124
  }
123
- this.session.debug(
125
+ this.session.log.debug(
124
126
  `received contact presence update from ${stanza.attrs.from}`,
125
127
  );
126
128
  this.session.sendToClient(obj);
127
129
  }
128
130
 
129
131
  subscribe(to, from, name) {
130
- this.session.debug(`received subscribe request from ${from}`);
132
+ this.session.log.debug(`received subscribe request from ${from}`);
131
133
  const actor = { id: from, type: "person" };
132
134
  if (name) {
133
135
  actor.name = name;
@@ -141,7 +143,7 @@ export class IncomingHandlers {
141
143
  }
142
144
 
143
145
  // unsubscribe(from) {
144
- // this.session.debug('received unsubscribe request from ' + from);
146
+ // this.session.log.debug('received unsubscribe request from ' + from);
145
147
  // this.session.sendToClient({
146
148
  // type: "remove-friend",
147
149
  // actor: { id: from },
@@ -262,7 +264,7 @@ export class IncomingHandlers {
262
264
  }
263
265
 
264
266
  online() {
265
- this.session.debug("online");
267
+ this.session.log.debug("online");
266
268
  }
267
269
 
268
270
  /**
@@ -281,7 +283,7 @@ export class IncomingHandlers {
281
283
  stanza.attrs.id === "muc_id" &&
282
284
  stanza.attrs.type === "result"
283
285
  ) {
284
- this.session.debug("got room attendance list");
286
+ this.session.log.debug("got room attendance list");
285
287
  return this.notifyRoomAttendance(stanza);
286
288
  }
287
289
 
@@ -293,7 +295,7 @@ export class IncomingHandlers {
293
295
  if (!entries.hasOwn(e)) {
294
296
  continue;
295
297
  }
296
- this.session.debug("STANZA ATTRS: ", entries[e].attrs);
298
+ this.session.log.debug("STANZA ATTRS: ", entries[e].attrs);
297
299
  if (entries[e].attrs.subscription === "both") {
298
300
  this.session.sendToClient({
299
301
  context: "xmpp",
@@ -342,7 +344,7 @@ export class IncomingHandlers {
342
344
  }
343
345
  }
344
346
  } else {
345
- this.session.debug(`got XMPP unknown stanza... ${stanza}`);
347
+ this.session.log.debug(`got XMPP unknown stanza... ${stanza}`);
346
348
  }
347
349
  }
348
350
  }
@@ -15,7 +15,7 @@ describe("Incoming handlers", () => {
15
15
  sendToClient = sinon.fake();
16
16
  ih = new IncomingHandlers({
17
17
  sendToClient: sendToClient,
18
- debug: sinon.fake(),
18
+ log: { debug: sinon.fake() },
19
19
  });
20
20
  });
21
21
 
@@ -43,11 +43,16 @@ describe("Incoming handlers", () => {
43
43
 
44
44
  it("close() should handle session without actor gracefully", () => {
45
45
  const sendToClient = sinon.fake();
46
- const debug = sinon.fake();
46
+ const log = {
47
+ error: sinon.fake(),
48
+ warn: sinon.fake(),
49
+ info: sinon.fake(),
50
+ debug: sinon.fake(),
51
+ };
47
52
 
48
53
  const ih = new IncomingHandlers({
49
54
  sendToClient: sendToClient,
50
- debug: debug,
55
+ log: log,
51
56
  actor: undefined,
52
57
  connection: undefined
53
58
  });
@@ -55,17 +60,22 @@ describe("Incoming handlers", () => {
55
60
  // This should not throw an error
56
61
  expect(() => ih.close()).not.toThrow();
57
62
 
58
- // Should still call debug
59
- sinon.assert.calledWith(debug, "received close event with no handler specified");
63
+ // Should still call log.debug
64
+ sinon.assert.calledWith(log.debug, "received close event with no handler specified");
60
65
  });
61
66
 
62
67
  it("close() should handle session without connection gracefully", () => {
63
68
  const sendToClient = sinon.fake();
64
- const debug = sinon.fake();
69
+ const log = {
70
+ error: sinon.fake(),
71
+ warn: sinon.fake(),
72
+ info: sinon.fake(),
73
+ debug: sinon.fake(),
74
+ };
65
75
 
66
76
  const ih = new IncomingHandlers({
67
77
  sendToClient: sendToClient,
68
- debug: debug,
78
+ log: log,
69
79
  actor: { id: "test@example.com" },
70
80
  connection: undefined
71
81
  });
@@ -76,11 +86,16 @@ describe("Incoming handlers", () => {
76
86
 
77
87
  it("close() should handle session with invalid connection gracefully", () => {
78
88
  const sendToClient = sinon.fake();
79
- const debug = sinon.fake();
89
+ const log = {
90
+ error: sinon.fake(),
91
+ warn: sinon.fake(),
92
+ info: sinon.fake(),
93
+ debug: sinon.fake(),
94
+ };
80
95
 
81
96
  const ih = new IncomingHandlers({
82
97
  sendToClient: sendToClient,
83
- debug: debug,
98
+ log: log,
84
99
  actor: { id: "test@example.com" },
85
100
  connection: { disconnect: null } // invalid disconnect method
86
101
  });
package/src/index.js CHANGED
@@ -40,10 +40,10 @@ export default class XMPP {
40
40
  this.config = {
41
41
  connectTimeoutMs: 10000,
42
42
  persist: true,
43
- initialized: false,
44
43
  requireCredentials: ["connect"],
45
44
  };
46
- this.debug = session.debug;
45
+ this.__initialized = false; // Private state for initialization tracking
46
+ this.log = session.log;
47
47
  this.sendToClient = session.sendToClient;
48
48
  this.createClient();
49
49
  this.createXml();
@@ -61,15 +61,15 @@ export default class XMPP {
61
61
  * @param {boolean} stopReconnection - If true, stop automatic reconnection
62
62
  */
63
63
  __markDisconnected(stopReconnection = false) {
64
- this.debug(`marking client as disconnected for ${this.id}`);
64
+ this.log.debug(`marking client as disconnected for ${this.id}`);
65
65
 
66
66
  if (stopReconnection && this.__client) {
67
- this.debug(`stopping automatic reconnection for ${this.id}`);
67
+ this.log.debug(`stopping automatic reconnection for ${this.id}`);
68
68
  this.__client.stop();
69
69
  }
70
70
 
71
71
  this.__client = undefined;
72
- this.config.initialized = false;
72
+ this.__initialized = false;
73
73
  }
74
74
 
75
75
  /**
@@ -132,7 +132,7 @@ export default class XMPP {
132
132
  this.__client.status === "online"
133
133
  );
134
134
  } catch (err) {
135
- this.debug("Error checking client connection status:", err);
135
+ this.log.debug("Error checking client connection status:", err);
136
136
  return false;
137
137
  }
138
138
  }
@@ -183,6 +183,17 @@ export default class XMPP {
183
183
  return PlatformSchema;
184
184
  }
185
185
 
186
+ /**
187
+ * Returns whether the platform is ready to handle jobs.
188
+ * For XMPP, this means we have successfully connected to the server.
189
+ * During temporary network interruptions with automatic reconnection,
190
+ * remains true to allow queued jobs to retry rather than fail.
191
+ * @returns {boolean} true if ready to handle jobs
192
+ */
193
+ isInitialized() {
194
+ return this.__initialized;
195
+ }
196
+
186
197
  /**
187
198
  * Connect to the XMPP server.
188
199
  *
@@ -205,15 +216,17 @@ export default class XMPP {
205
216
  */
206
217
  connect(job, credentials, done) {
207
218
  if (this.__isClientConnected()) {
208
- this.debug(`client connection already exists for ${job.actor.id}`);
209
- this.config.initialized = true;
219
+ this.log.debug(
220
+ `client connection already exists for ${job.actor.id}`,
221
+ );
222
+ this.__initialized = true;
210
223
  return done();
211
224
  }
212
- this.debug(`connect() called for ${job.actor.id}`);
225
+ this.log.debug(`connect() called for ${job.actor.id}`);
213
226
 
214
227
  // Log credential processing
215
228
  const xmppCreds = utils.buildXmppCredentials(credentials);
216
- this.debug(
229
+ this.log.debug(
217
230
  `building XMPP credentials for ${job.actor.id}:`,
218
231
  JSON.stringify({
219
232
  service: xmppCreds.service,
@@ -224,26 +237,59 @@ export default class XMPP {
224
237
  );
225
238
 
226
239
  // Log before client creation
227
- this.debug(`creating XMPP client for ${job.actor.id}`);
240
+ this.log.debug(`creating XMPP client for ${job.actor.id}`);
228
241
 
229
242
  try {
230
243
  this.__client = this.__clientConstructor({
231
244
  ...xmppCreds,
232
245
  ...{ timeout: this.config.connectTimeoutMs, tls: false },
233
246
  });
234
- this.debug(`XMPP client created successfully for ${job.actor.id}`);
247
+ this.log.debug(
248
+ `XMPP client created successfully for ${job.actor.id}`,
249
+ );
235
250
  } catch (err) {
236
- this.debug(`XMPP client creation failed for ${job.actor.id}:`, err);
251
+ this.log.debug(
252
+ `XMPP client creation failed for ${job.actor.id}:`,
253
+ err,
254
+ );
237
255
  return done(`client creation failed: ${err.message}`);
238
256
  }
239
257
 
240
258
  this.__client.on("offline", () => {
241
- this.debug(`offline event received for ${job.actor.id}`);
242
- this.__markDisconnected();
259
+ this.log.debug(`offline event received for ${job.actor.id}`);
260
+ // If we were never initialized, mark as disconnected (connection failed)
261
+ // If we were previously initialized, keep state (will auto-reconnect)
262
+ // This preserves initialized state during brief network interruptions
263
+ // while properly handling initial connection failures
264
+ if (!this.__initialized) {
265
+ this.log.debug(
266
+ `offline during initial connection for ${job.actor.id}`,
267
+ );
268
+ this.__markDisconnected();
269
+ } else {
270
+ this.log.debug(
271
+ `offline after successful connection for ${job.actor.id}, will auto-reconnect`,
272
+ );
273
+ }
243
274
  });
244
275
 
245
276
  this.__client.on("error", (err) => {
246
- this.debug(
277
+ // Internal code errors (TypeError, ReferenceError, etc.) indicate bugs
278
+ // in our code. These should crash the platform process immediately
279
+ // as we can't trust the state after such errors.
280
+ if (
281
+ err instanceof TypeError ||
282
+ err instanceof ReferenceError ||
283
+ err instanceof SyntaxError
284
+ ) {
285
+ this.log.error(
286
+ `FATAL: Internal code error in XMPP platform: ${err.toString()}`,
287
+ );
288
+ this.log.error(err.stack);
289
+ process.exit(1);
290
+ }
291
+
292
+ this.log.debug(
247
293
  `network error event for ${job.actor.id}:${err.toString()}`,
248
294
  );
249
295
 
@@ -256,9 +302,9 @@ export default class XMPP {
256
302
  };
257
303
 
258
304
  if (errorType === "RECOVERABLE") {
259
- // Clean up state but allow reconnection
260
- this.__markDisconnected(false);
261
-
305
+ // For recoverable errors, keep initialized=true and let the client
306
+ // auto-reconnect. Don't call stop() or clear the client reference.
307
+ // This allows queued jobs to wait for reconnection instead of failing.
262
308
  as.error = `Connection lost: ${err.toString()}. Attempting automatic reconnection...`;
263
309
  as.object = {
264
310
  type: "connect",
@@ -266,7 +312,7 @@ export default class XMPP {
266
312
  condition: err.condition || "network",
267
313
  };
268
314
  } else {
269
- // Clean up state and stop reconnection
315
+ // On unrecoverable errors, mark as uninitialized and stop reconnection
270
316
  this.__markDisconnected(true);
271
317
 
272
318
  as.error = `Connection failed: ${err.toString()}. Manual reconnection required.`;
@@ -280,10 +326,10 @@ export default class XMPP {
280
326
  });
281
327
 
282
328
  this.__client.on("online", () => {
283
- this.debug(`online event received for ${job.actor.id}`);
329
+ this.log.debug(`online event received for ${job.actor.id}`);
284
330
  });
285
331
 
286
- this.debug(`starting XMPP client connection for ${job.actor.id}`);
332
+ this.log.debug(`starting XMPP client connection for ${job.actor.id}`);
287
333
  const startTime = Date.now();
288
334
 
289
335
  this.__client
@@ -291,16 +337,16 @@ export default class XMPP {
291
337
  .then(() => {
292
338
  // connected
293
339
  const duration = Date.now() - startTime;
294
- this.debug(
340
+ this.log.debug(
295
341
  `connection successful for ${job.actor.id} after ${duration}ms`,
296
342
  );
297
- this.config.initialized = true;
343
+ this.__initialized = true;
298
344
  this.__registerHandlers();
299
345
  return done();
300
346
  })
301
347
  .catch((err) => {
302
348
  const duration = Date.now() - startTime;
303
- this.debug(
349
+ this.log.debug(
304
350
  `connection failed for ${job.actor.id} after ${duration}ms:`,
305
351
  {
306
352
  error: err,
@@ -339,7 +385,7 @@ export default class XMPP {
339
385
  * }
340
386
  */
341
387
  async join(job, done) {
342
- this.debug(
388
+ this.log.debug(
343
389
  `sending join from ${job.actor.id} to ` +
344
390
  `${job.target.id}/${job.actor.name}`,
345
391
  );
@@ -382,7 +428,7 @@ export default class XMPP {
382
428
  * }
383
429
  */
384
430
  leave(job, done) {
385
- this.debug(
431
+ this.log.debug(
386
432
  `sending leave from ${job.actor.id} to ` +
387
433
  `${job.target.id}/${job.actor.name}`,
388
434
  );
@@ -452,7 +498,7 @@ export default class XMPP {
452
498
  *
453
499
  */
454
500
  send(job, done) {
455
- this.debug(`send() called for ${job.actor.id}`);
501
+ this.log.debug(`send() called for ${job.actor.id}`);
456
502
  // send message
457
503
  const message = this.__xml(
458
504
  "message",
@@ -496,7 +542,7 @@ export default class XMPP {
496
542
  * }
497
543
  */
498
544
  update(job, done) {
499
- this.debug(`update() called for ${job.actor.id}`);
545
+ this.log.debug(`update() called for ${job.actor.id}`);
500
546
  const props = {};
501
547
  const show = {};
502
548
  const status = {};
@@ -510,7 +556,7 @@ export default class XMPP {
510
556
  status.status = job.object.content;
511
557
  }
512
558
  // setting presence
513
- this.debug(`setting presence: ${job.object.presence}`);
559
+ this.log.debug(`setting presence: ${job.object.presence}`);
514
560
  this.__client
515
561
  .send(this.__xml("presence", props, show, status))
516
562
  .then(done);
@@ -540,7 +586,7 @@ export default class XMPP {
540
586
  * }
541
587
  */
542
588
  "request-friend"(job, done) {
543
- this.debug(`request-friend() called for ${job.actor.id}`);
589
+ this.log.debug(`request-friend() called for ${job.actor.id}`);
544
590
  this.__client
545
591
  .send(
546
592
  this.__xml("presence", {
@@ -572,7 +618,7 @@ export default class XMPP {
572
618
  * }
573
619
  */
574
620
  "remove-friend"(job, done) {
575
- this.debug(`remove-friend() called for ${job.actor.id}`);
621
+ this.log.debug(`remove-friend() called for ${job.actor.id}`);
576
622
  this.__client
577
623
  .send(
578
624
  this.__xml("presence", {
@@ -604,7 +650,7 @@ export default class XMPP {
604
650
  * }
605
651
  */
606
652
  "make-friend"(job, done) {
607
- this.debug(`make-friend() called for ${job.actor.id}`);
653
+ this.log.debug(`make-friend() called for ${job.actor.id}`);
608
654
  this.__client
609
655
  .send(
610
656
  this.__xml("presence", {
@@ -664,7 +710,9 @@ export default class XMPP {
664
710
  * }
665
711
  */
666
712
  query(job, done) {
667
- this.debug(`sending query from ${job.actor.id} for ${job.target.id}`);
713
+ this.log.debug(
714
+ `sending query from ${job.actor.id} for ${job.target.id}`,
715
+ );
668
716
  this.__client
669
717
  .send(
670
718
  this.__xml(
@@ -700,7 +748,7 @@ export default class XMPP {
700
748
  * }
701
749
  */
702
750
  disconnect(job, done) {
703
- this.debug("disconnecting");
751
+ this.log.debug("disconnecting");
704
752
  this.cleanup(done);
705
753
  }
706
754
 
@@ -710,8 +758,8 @@ export default class XMPP {
710
758
  * @param {function} done - callback when complete
711
759
  */
712
760
  cleanup(done) {
713
- this.debug("cleanup");
714
- this.config.initialized = false;
761
+ this.log.debug("cleanup");
762
+ this.__initialized = false;
715
763
  this.__client.stop();
716
764
  done();
717
765
  }
package/src/index.test.js CHANGED
@@ -185,7 +185,12 @@ describe("XMPP", () => {
185
185
 
186
186
  xp = new TestXMPP({
187
187
  id: actor,
188
- debug: sinon.fake(),
188
+ log: {
189
+ error: sinon.fake(),
190
+ warn: sinon.fake(),
191
+ info: sinon.fake(),
192
+ debug: sinon.fake(),
193
+ },
189
194
  sendToClient: sinon.fake(),
190
195
  });
191
196
  });
@@ -240,6 +245,107 @@ describe("XMPP", () => {
240
245
  });
241
246
  });
242
247
 
248
+ describe("Error handling", () => {
249
+ let processExitStub;
250
+
251
+ beforeEach(() => {
252
+ // Stub process.exit to prevent test process from exiting
253
+ processExitStub = sinon.stub(process, "exit");
254
+ });
255
+
256
+ afterEach(() => {
257
+ processExitStub.restore();
258
+ });
259
+
260
+ it("terminates process on TypeError (internal code error)", (done) => {
261
+ xp.connect(job.connect, credentials, () => {
262
+ // Get the error event handler that was registered
263
+ const errorHandler = clientObjectFake.on.getCalls().find(
264
+ (call) => call.args[0] === "error",
265
+ ).args[1];
266
+
267
+ // Simulate a TypeError (internal code error)
268
+ const typeError = new TypeError("this.session.debug is not a function");
269
+ errorHandler(typeError);
270
+
271
+ // Verify process.exit(1) was called
272
+ sinon.assert.calledOnce(processExitStub);
273
+ sinon.assert.calledWith(processExitStub, 1);
274
+
275
+ // Verify error was logged
276
+ sinon.assert.called(xp.log.error);
277
+ expect(
278
+ xp.log.error
279
+ .getCalls()
280
+ .some((call) =>
281
+ call.args[0].includes("FATAL: Internal code error"),
282
+ ),
283
+ ).toBeTrue();
284
+
285
+ done();
286
+ });
287
+ });
288
+
289
+ it("terminates process on ReferenceError (internal code error)", (done) => {
290
+ xp.connect(job.connect, credentials, () => {
291
+ const errorHandler = clientObjectFake.on.getCalls().find(
292
+ (call) => call.args[0] === "error",
293
+ ).args[1];
294
+
295
+ const refError = new ReferenceError("foo is not defined");
296
+ errorHandler(refError);
297
+
298
+ sinon.assert.calledOnce(processExitStub);
299
+ sinon.assert.calledWith(processExitStub, 1);
300
+ sinon.assert.called(xp.log.error);
301
+
302
+ done();
303
+ });
304
+ });
305
+
306
+ it("does NOT terminate process on network errors (recoverable)", (done) => {
307
+ xp.connect(job.connect, credentials, () => {
308
+ const errorHandler = clientObjectFake.on.getCalls().find(
309
+ (call) => call.args[0] === "error",
310
+ ).args[1];
311
+
312
+ // Simulate a network error
313
+ const networkError = new Error("ECONNRESET");
314
+ networkError.code = "ECONNRESET";
315
+ errorHandler(networkError);
316
+
317
+ // Should NOT call process.exit for network errors
318
+ sinon.assert.notCalled(processExitStub);
319
+
320
+ // Should send error to client instead
321
+ sinon.assert.called(xp.sendToClient);
322
+
323
+ done();
324
+ });
325
+ });
326
+
327
+ it("does NOT terminate process on XMPP protocol errors (non-recoverable)", (done) => {
328
+ xp.connect(job.connect, credentials, () => {
329
+ const errorHandler = clientObjectFake.on.getCalls().find(
330
+ (call) => call.args[0] === "error",
331
+ ).args[1];
332
+
333
+ // Simulate an XMPP protocol error (e.g., auth failure)
334
+ const authError = new Error("not-authorized");
335
+ authError.condition = "not-authorized";
336
+ errorHandler(authError);
337
+
338
+ // Should NOT call process.exit for XMPP errors
339
+ sinon.assert.notCalled(processExitStub);
340
+
341
+ // Should send error to client and mark disconnected
342
+ sinon.assert.called(xp.sendToClient);
343
+
344
+ done();
345
+ });
346
+ });
347
+ });
348
+
243
349
  describe("Platform functionality", () => {
244
350
  beforeEach((done) => {
245
351
  xp.connect(job.join, credentials, () => done());
@@ -484,9 +590,9 @@ describe("XMPP", () => {
484
590
 
485
591
  describe("#cleanup", () => {
486
592
  it("calls client.stop", (done) => {
487
- expect(xp.config.initialized).toEqual(true);
593
+ expect(xp.isInitialized()).toEqual(true);
488
594
  xp.cleanup(() => {
489
- expect(xp.config.initialized).toEqual(false);
595
+ expect(xp.isInitialized()).toEqual(false);
490
596
  sinon.assert.calledOnce(xp.__client.stop);
491
597
  done()
492
598
  });