@adobe/ccweb-add-on-ssl 2.5.0 → 3.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.
Files changed (57) hide show
  1. package/.mocharc.json +9 -2
  2. package/README.md +5 -4
  3. package/bin/run.js +3 -2
  4. package/dist/AnalyticsMarkers.d.ts +5 -2
  5. package/dist/AnalyticsMarkers.d.ts.map +1 -1
  6. package/dist/AnalyticsMarkers.js +3 -0
  7. package/dist/app/CommandExecutor.d.ts +1 -1
  8. package/dist/app/CommandExecutor.d.ts.map +1 -1
  9. package/dist/app/PurgeCommandExecutor.d.ts +53 -0
  10. package/dist/app/PurgeCommandExecutor.d.ts.map +1 -0
  11. package/dist/app/PurgeCommandExecutor.js +125 -0
  12. package/dist/app/SSLReader.d.ts +2 -1
  13. package/dist/app/SSLReader.d.ts.map +1 -1
  14. package/dist/app/SetupCommandExecutor.d.ts.map +1 -1
  15. package/dist/app/SetupCommandExecutor.js +10 -7
  16. package/dist/app/WxpSSLReader.d.ts +8 -1
  17. package/dist/app/WxpSSLReader.d.ts.map +1 -1
  18. package/dist/app/WxpSSLReader.js +59 -5
  19. package/dist/app/index.d.ts +2 -1
  20. package/dist/app/index.d.ts.map +1 -1
  21. package/dist/app/index.js +2 -1
  22. package/dist/commands/purge.d.ts +41 -0
  23. package/dist/commands/purge.d.ts.map +1 -0
  24. package/dist/commands/purge.js +56 -0
  25. package/dist/commands/setup.d.ts +5 -10
  26. package/dist/commands/setup.d.ts.map +1 -1
  27. package/dist/commands/setup.js +5 -32
  28. package/dist/config/inversify.config.d.ts +2 -1
  29. package/dist/config/inversify.config.d.ts.map +1 -1
  30. package/dist/config/inversify.config.js +6 -1
  31. package/dist/config/inversify.types.d.ts.map +1 -1
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -0
  35. package/dist/models/Types.d.ts +0 -1
  36. package/dist/models/Types.d.ts.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +14 -12
  39. package/src/AnalyticsMarkers.ts +5 -2
  40. package/src/app/CommandExecutor.ts +1 -1
  41. package/src/app/PurgeCommandExecutor.ts +144 -0
  42. package/src/app/SSLReader.ts +2 -1
  43. package/src/app/SetupCommandExecutor.ts +10 -7
  44. package/src/app/WxpSSLReader.ts +71 -5
  45. package/src/app/index.ts +2 -1
  46. package/src/commands/purge.ts +72 -0
  47. package/src/commands/setup.ts +12 -40
  48. package/src/config/inversify.config.ts +9 -2
  49. package/src/config/inversify.types.ts +5 -1
  50. package/src/index.ts +2 -0
  51. package/src/test/app/PurgeCommandExecutor.spec.ts +195 -0
  52. package/src/test/app/SetupCommandExecutor.spec.ts +141 -34
  53. package/src/test/app/WxpSSLReader.spec.ts +107 -33
  54. package/src/test/commands/command.spec.ts +108 -0
  55. package/src/test/commands/purge.spec.ts +132 -0
  56. package/src/test/commands/setup.spec.ts +28 -29
  57. package/tsconfig.json +3 -1
@@ -0,0 +1,195 @@
1
+ /********************************************************************************
2
+ * MIT License
3
+
4
+ * © Copyright 2025 Adobe. All rights reserved.
5
+
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ ********************************************************************************/
24
+
25
+ import type { AnalyticsService } from "@adobe/ccweb-add-on-analytics";
26
+ import { PreferenceJson, type Logger, type Preferences } from "@adobe/ccweb-add-on-core";
27
+ import devcert from "@adobe/ccweb-add-on-devcert";
28
+ import { assert } from "chai";
29
+ import chalk from "chalk";
30
+ import fs from "fs-extra";
31
+ import "mocha";
32
+ import path from "path";
33
+ import prompts from "prompts";
34
+ import type { SinonSandbox } from "sinon";
35
+ import sinon from "sinon";
36
+ import type { StubbedInstance } from "ts-sinon";
37
+ import { stubInterface } from "ts-sinon";
38
+ import { AnalyticsErrorMarkers, AnalyticsSuccessMarkers } from "../../AnalyticsMarkers.js";
39
+ import type { CommandExecutor } from "../../app/index.js";
40
+ import { PurgeCommandExecutor } from "../../app/index.js";
41
+ import { SSLRemoveOption } from "../../models/index.js";
42
+
43
+ describe("PurgeCommandExecutor", () => {
44
+ describe("execute", () => {
45
+ let sandbox: SinonSandbox;
46
+
47
+ let preferences: StubbedInstance<Preferences>;
48
+ let analyticsService: StubbedInstance<AnalyticsService>;
49
+ let logger: StubbedInstance<Logger>;
50
+
51
+ let commandExecutor: CommandExecutor;
52
+
53
+ beforeEach(() => {
54
+ sandbox = sinon.createSandbox();
55
+
56
+ preferences = stubInterface();
57
+ analyticsService = stubInterface();
58
+ logger = stubInterface();
59
+
60
+ commandExecutor = new PurgeCommandExecutor(preferences, analyticsService, logger);
61
+ });
62
+
63
+ afterEach(() => {
64
+ sandbox.restore();
65
+ });
66
+
67
+ const shouldPurgePrompt = {
68
+ type: "select",
69
+ name: "purgeConfirmation",
70
+ message: promptMessage("Are you sure you want to remove all SSL artifacts from your system"),
71
+ choices: [
72
+ { title: promptMessageOption("No, keep existing SSL artifacts"), value: SSLRemoveOption.Keep },
73
+ { title: promptMessageOption("Yes, remove all SSL artifacts"), value: SSLRemoveOption.Remove }
74
+ ],
75
+ initial: 0
76
+ };
77
+
78
+ it("should prompt on whether to remove SSL artifacts and log warning and exit when user does not select any option.", async () => {
79
+ const promptStub = sandbox.stub(prompts, "prompt");
80
+ promptStub.withArgs(shouldPurgePrompt).resolves({ purgeConfirmation: undefined });
81
+
82
+ analyticsService.postEvent.resolves();
83
+
84
+ await commandExecutor.execute();
85
+
86
+ assert.equal(
87
+ analyticsService.postEvent.calledOnceWith(
88
+ AnalyticsErrorMarkers.ERROR_SSL_PURGE,
89
+ "SSL purge option is not specified.",
90
+ false
91
+ ),
92
+ true
93
+ );
94
+ });
95
+
96
+ it("should prompt on whether to remove SSL artifacts and exit when user chooses to keep the existing.", async () => {
97
+ const promptStub = sandbox.stub(prompts, "prompt");
98
+ promptStub.withArgs(shouldPurgePrompt).resolves({ purgeConfirmation: SSLRemoveOption.Keep });
99
+
100
+ await commandExecutor.execute();
101
+
102
+ assert.equal(analyticsService.postEvent.notCalled, true);
103
+ assert.equal(logger.information.notCalled, true);
104
+ });
105
+
106
+ it("should prompt on whether to remove SSL artifacts and remove both custom and WXP SSL artifacts when user chooses to remove.", async () => {
107
+ const promptStub = sandbox.stub(prompts, "prompt");
108
+ promptStub.withArgs(shouldPurgePrompt).resolves({ purgeConfirmation: SSLRemoveOption.Remove });
109
+
110
+ const preferenceJson = new PreferenceJson({
111
+ ssl: [
112
+ [
113
+ "localhost",
114
+ {
115
+ certificatePath: path.join("custom", "localhost", "certificate.cert"),
116
+ keyPath: path.join("custom", "localhost", "private-key.key")
117
+ }
118
+ ],
119
+ [
120
+ "localhost.adobe.com",
121
+ {
122
+ certificatePath: path.join("custom", "localhost.adobe.com", "certificate.cert"),
123
+ keyPath: path.join("custom", "localhost.adobe.com", "private-key.key")
124
+ }
125
+ ]
126
+ ]
127
+ });
128
+ preferences.get.returns(preferenceJson);
129
+
130
+ const wxpSSLLocation = path.join("wxp", "devcert");
131
+ sandbox.stub(devcert, "location").returns(wxpSSLLocation);
132
+
133
+ const existsStub = sandbox.stub(fs, "existsSync");
134
+ existsStub.withArgs(wxpSSLLocation).returns(true);
135
+
136
+ const removeAllStub = sandbox.stub(devcert, "removeAll");
137
+ removeAllStub.returns();
138
+
139
+ await commandExecutor.execute();
140
+
141
+ assert.equal(
142
+ logger.information.calledOnceWith("Removing and invalidating all SSL artifacts from your system ...", {
143
+ prefix: "\n"
144
+ }),
145
+ true
146
+ );
147
+ assert.equal(
148
+ logger.message.getCall(0).calledWith("This may require you to enter your system's password,"),
149
+ true
150
+ );
151
+ assert.equal(
152
+ logger.message
153
+ .getCall(1)
154
+ .calledWith(
155
+ "so that the SSL certificate can be removed from your system's trusted certificate path."
156
+ ),
157
+ true
158
+ );
159
+ assert.equal(
160
+ logger.message.getCall(2).calledWith("[If this takes longer than expected, please break and retry]"),
161
+ true
162
+ );
163
+
164
+ preferenceJson.ssl = undefined;
165
+ assert.equal(preferences.set.calledOnceWith(preferenceJson), true);
166
+ assert.equal(
167
+ analyticsService.postEvent
168
+ .getCall(0)
169
+ .calledWith(AnalyticsSuccessMarkers.SUCCESSFUL_SSL_MANUAL_PURGE, "", true),
170
+ true
171
+ );
172
+
173
+ assert.equal(removeAllStub.calledOnce, true);
174
+ assert.equal(
175
+ analyticsService.postEvent
176
+ .getCall(1)
177
+ .calledWith(AnalyticsSuccessMarkers.SUCCESSFUL_SSL_AUTOMATIC_PURGE, "", true),
178
+ true
179
+ );
180
+
181
+ assert.equal(
182
+ logger.success.calledWith("Removed all SSL artifacts.", { prefix: "\n", postfix: "\n" }),
183
+ true
184
+ );
185
+ });
186
+ });
187
+ });
188
+
189
+ function promptMessage(message: string): string {
190
+ return chalk.hex("#E59400")(message);
191
+ }
192
+
193
+ function promptMessageOption(message: string): string {
194
+ return chalk.green.bold(message);
195
+ }
@@ -25,10 +25,10 @@
25
25
  import type { AnalyticsService } from "@adobe/ccweb-add-on-analytics";
26
26
  import type { Logger, Preferences } from "@adobe/ccweb-add-on-core";
27
27
  import { ADD_ON_PREFERENCES_FILE, PreferenceJson } from "@adobe/ccweb-add-on-core";
28
+ import devcert from "@adobe/ccweb-add-on-devcert";
28
29
  import chai, { assert, expect } from "chai";
29
30
  import chaiAsPromised from "chai-as-promised";
30
31
  import chalk from "chalk";
31
- import devcert from "@adobe/ccweb-add-on-devcert";
32
32
  import type { Stats } from "fs-extra";
33
33
  import fs from "fs-extra";
34
34
  import "mocha";
@@ -47,7 +47,7 @@ import { SSLRemoveOption, SSLSetupOption, SetupCommandOptions } from "../../mode
47
47
 
48
48
  chai.use(chaiAsPromised);
49
49
 
50
- describe("SSLSetupCommandExecutor", () => {
50
+ describe("SetupCommandExecutor", () => {
51
51
  let sandbox: SinonSandbox;
52
52
 
53
53
  let preferences: StubbedInstance<Preferences>;
@@ -140,8 +140,6 @@ describe("SSLSetupCommandExecutor", () => {
140
140
 
141
141
  analyticsService.postEvent.resolves();
142
142
 
143
- logger.warning.returns();
144
-
145
143
  const processExitStub = sandbox.stub(process, "exit");
146
144
 
147
145
  const options = new SetupCommandOptions(hostname, true, false);
@@ -191,7 +189,6 @@ describe("SSLSetupCommandExecutor", () => {
191
189
 
192
190
  analyticsService.postEvent.resolves();
193
191
 
194
- logger.warning.returns();
195
192
  logger.error.returns();
196
193
 
197
194
  const processExitStub = sandbox.stub(process, "exit");
@@ -280,7 +277,6 @@ describe("SSLSetupCommandExecutor", () => {
280
277
 
281
278
  analyticsService.postEvent.resolves();
282
279
 
283
- logger.warning.returns();
284
280
  logger.error.returns();
285
281
 
286
282
  const processExitStub = sandbox.stub(process, "exit");
@@ -355,9 +351,6 @@ describe("SSLSetupCommandExecutor", () => {
355
351
 
356
352
  analyticsService.postEvent.resolves();
357
353
 
358
- logger.warning.returns();
359
- logger.success.returns();
360
-
361
354
  const existsStub = sandbox.stub(fs, "existsSync");
362
355
  existsStub.withArgs(certificatePath).returns(true);
363
356
  existsStub.withArgs(keyPath).returns(true);
@@ -422,10 +415,6 @@ describe("SSLSetupCommandExecutor", () => {
422
415
 
423
416
  analyticsService.postEvent.resolves();
424
417
 
425
- logger.warning.returns();
426
- logger.success.returns();
427
- logger.message.returns();
428
-
429
418
  const options = new SetupCommandOptions(hostname, true, false);
430
419
  await commandExecutor.execute(options);
431
420
 
@@ -444,7 +433,9 @@ describe("SSLSetupCommandExecutor", () => {
444
433
  assert.equal(
445
434
  logger.message
446
435
  .getCall(0)
447
- .calledWith("This is only a one time step and may require you to enter your system's password"),
436
+ .calledWith(
437
+ "This is only a one time step and may require you to enter your system's password,"
438
+ ),
448
439
  true
449
440
  );
450
441
  assert.equal(
@@ -505,7 +496,7 @@ describe("SSLSetupCommandExecutor", () => {
505
496
  initial: 0
506
497
  };
507
498
 
508
- it("should prompt on whether to remove existing SSL and log warning and exit when user does not select any option.", async () => {
499
+ it("should prompt on whether to remove existing SSL and exit when user does not select any option.", async () => {
509
500
  const hostname = "localhost";
510
501
  sslReader.isCustomSSL.withArgs(hostname).returns(true);
511
502
  sslReader.isWxpSSL.withArgs(hostname).returns(false);
@@ -515,8 +506,6 @@ describe("SSLSetupCommandExecutor", () => {
515
506
 
516
507
  analyticsService.postEvent.resolves();
517
508
 
518
- logger.warning.returns();
519
-
520
509
  const processExitStub = sandbox.stub(process, "exit");
521
510
 
522
511
  const exitError = new Error("Process exit.");
@@ -562,8 +551,6 @@ describe("SSLSetupCommandExecutor", () => {
562
551
 
563
552
  analyticsService.postEvent.resolves();
564
553
 
565
- logger.warning.returns();
566
-
567
554
  const options = new SetupCommandOptions(hostname, false, false);
568
555
  await commandExecutor.execute(options);
569
556
 
@@ -597,8 +584,6 @@ describe("SSLSetupCommandExecutor", () => {
597
584
 
598
585
  analyticsService.postEvent.resolves();
599
586
 
600
- logger.warning.returns();
601
-
602
587
  preferences.get.onCall(0).returns(
603
588
  new PreferenceJson({
604
589
  ssl: {
@@ -620,9 +605,6 @@ describe("SSLSetupCommandExecutor", () => {
620
605
 
621
606
  analyticsService.postEvent.resolves();
622
607
 
623
- logger.warning.returns();
624
- logger.success.returns();
625
-
626
608
  const existsStub = sandbox.stub(fs, "existsSync");
627
609
  existsStub.withArgs(certificatePath).returns(true);
628
610
  existsStub.withArgs(keyPath).returns(true);
@@ -657,7 +639,12 @@ describe("SSLSetupCommandExecutor", () => {
657
639
  true
658
640
  );
659
641
 
660
- assert.equal(logger.success.getCall(0).calledWith("Removed.", { postfix: "\n" }), true);
642
+ assert.equal(
643
+ logger.success
644
+ .getCall(0)
645
+ .calledWith("Removed existing SSL certificate.", { prefix: "\n", postfix: "\n" }),
646
+ true
647
+ );
661
648
 
662
649
  assert.equal(
663
650
  logger.success
@@ -674,7 +661,7 @@ describe("SSLSetupCommandExecutor", () => {
674
661
  );
675
662
  });
676
663
 
677
- it("should remove existing and create a new one when user chooses to remove an automatically set up SSL.", async () => {
664
+ it("should re-create an SSL certificate when user chooses to remove a valid, automatically set up SSL.", async () => {
678
665
  const hostname = "localhost";
679
666
 
680
667
  // SSL remove.
@@ -686,8 +673,6 @@ describe("SSLSetupCommandExecutor", () => {
686
673
 
687
674
  analyticsService.postEvent.resolves();
688
675
 
689
- logger.warning.returns();
690
-
691
676
  // SSL setup.
692
677
  promptStub.withArgs(sslSetupTypePrompt).resolves({
693
678
  sslSetupType: SSLSetupOption.Automatically
@@ -705,6 +690,8 @@ describe("SSLSetupCommandExecutor", () => {
705
690
  // @ts-ignore -- IReturnData mock response
706
691
  .resolves({ cert: <Buffer>{}, key: <Buffer>{}, caPath });
707
692
 
693
+ sandbox.stub(devcert, "caExpiryInDays").returns(100);
694
+
708
695
  const removeDomainStub = sandbox.stub(devcert, "removeDomain");
709
696
  removeDomainStub.withArgs(hostname).resolves();
710
697
 
@@ -712,10 +699,6 @@ describe("SSLSetupCommandExecutor", () => {
712
699
 
713
700
  analyticsService.postEvent.resolves();
714
701
 
715
- logger.warning.returns();
716
- logger.success.returns();
717
- logger.message.returns();
718
-
719
702
  const options = new SetupCommandOptions(hostname, false, false);
720
703
  await commandExecutor.execute(options);
721
704
 
@@ -741,13 +724,137 @@ describe("SSLSetupCommandExecutor", () => {
741
724
  true
742
725
  );
743
726
 
744
- assert.equal(logger.success.getCall(0).calledWith("Removed.", { postfix: "\n" }), true);
727
+ assert.equal(
728
+ logger.success
729
+ .getCall(0)
730
+ .calledWith("Removed existing SSL certificate.", { prefix: "\n", postfix: "\n" }),
731
+ true
732
+ );
733
+ assert.equal(logger.success.getCall(1).calledWith("SSL setup complete!", { prefix: "\n" }), true);
734
+
735
+ assert.equal(
736
+ logger.message
737
+ .getCall(0)
738
+ .calledWith(
739
+ "This is only a one time step and may require you to enter your system's password,"
740
+ ),
741
+ true
742
+ );
743
+ assert.equal(
744
+ logger.message
745
+ .getCall(1)
746
+ .calledWith(
747
+ "so that the SSL certificate can be added to your system's trusted certificate path."
748
+ ),
749
+ true
750
+ );
751
+ assert.equal(
752
+ logger.message
753
+ .getCall(2)
754
+ .calledWith("[If this takes longer than expected, please break and retry]"),
755
+ true
756
+ );
757
+
758
+ assert.equal(
759
+ logger.information
760
+ .getCall(0)
761
+ .calledWith("Setting up self-signed SSL certificate ...", { prefix: "\n" }),
762
+ true
763
+ );
764
+ assert.equal(
765
+ logger.information
766
+ .getCall(1)
767
+ .calledWith(format("You can find the SSL certificate in {sslDirectory}.", { sslDirectory }), {
768
+ postfix: "\n"
769
+ }),
770
+ true
771
+ );
772
+
773
+ assert.equal(
774
+ analyticsService.postEvent
775
+ .getCall(1)
776
+ .calledWith(AnalyticsSuccessMarkers.SUCCESSFUL_SSL_AUTOMATIC_SETUP, options.hostname, true),
777
+ true
778
+ );
779
+ });
780
+
781
+ it("should re-create an SSL certificate when user chooses to remove an expired, automatically set up SSL.", async () => {
782
+ const hostname = "localhost";
783
+
784
+ // SSL remove.
785
+ sslReader.isCustomSSL.withArgs(hostname).returns(false);
786
+ sslReader.isWxpSSL.withArgs(hostname).returns(true);
787
+
788
+ const promptStub = sandbox.stub(prompts, "prompt");
789
+ promptStub.withArgs(shouldRemovePrompt).resolves({ shouldRemove: SSLRemoveOption.Remove });
790
+
791
+ analyticsService.postEvent.resolves();
792
+
793
+ // SSL setup.
794
+ promptStub.withArgs(sslSetupTypePrompt).resolves({
795
+ sslSetupType: SSLSetupOption.Automatically
796
+ });
797
+
798
+ preferences.get.onCall(1).returns(new PreferenceJson({}));
799
+ preferences.set.returns();
800
+
801
+ const caPath = "/some-directory/certificate-authority/certificate.cert";
802
+ const sslDirectory = `/some-directory/domains/${hostname}`;
803
+
804
+ sandbox
805
+ .stub(devcert, "certificateFor")
806
+ .withArgs(hostname, { getCaPath: true })
807
+ // @ts-ignore -- IReturnData mock response
808
+ .resolves({ cert: <Buffer>{}, key: <Buffer>{}, caPath });
809
+
810
+ sandbox.stub(devcert, "caExpiryInDays").returns(0);
811
+
812
+ const removeAllStub = sandbox.stub(devcert, "removeAll");
813
+ removeAllStub.returns();
814
+
815
+ sandbox.stub(path, "resolve").withArgs(caPath, "..", "..", "domains", hostname).returns(sslDirectory);
816
+
817
+ analyticsService.postEvent.resolves();
818
+
819
+ const options = new SetupCommandOptions(hostname, false, false);
820
+ await commandExecutor.execute(options);
821
+
822
+ assert.equal(removeAllStub.calledOnce, true);
823
+
824
+ assert.equal(
825
+ logger.warning.calledOnceWith(
826
+ format(
827
+ "A trusted SSL certificate is already configured for the Add-on to run on https://{hostname}",
828
+ { hostname: options.hostname }
829
+ ),
830
+ {
831
+ prefix: "\n"
832
+ }
833
+ ),
834
+ true
835
+ );
836
+
837
+ assert.equal(
838
+ analyticsService.postEvent
839
+ .getCall(0)
840
+ .calledWith(AnalyticsSuccessMarkers.SUCCESSFUL_SSL_AUTOMATIC_REMOVE, options.hostname, true),
841
+ true
842
+ );
843
+
844
+ assert.equal(
845
+ logger.success
846
+ .getCall(0)
847
+ .calledWith("Removed existing SSL certificate.", { prefix: "\n", postfix: "\n" }),
848
+ true
849
+ );
745
850
  assert.equal(logger.success.getCall(1).calledWith("SSL setup complete!", { prefix: "\n" }), true);
746
851
 
747
852
  assert.equal(
748
853
  logger.message
749
854
  .getCall(0)
750
- .calledWith("This is only a one time step and may require you to enter your system's password"),
855
+ .calledWith(
856
+ "This is only a one time step and may require you to enter your system's password,"
857
+ ),
751
858
  true
752
859
  );
753
860
  assert.equal(