]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - frontend/changes_test.go
safcm: add commit date to version output
[safcm/safcm.git] / frontend / changes_test.go
1 // Copyright (C) 2021  Simon Ruderich
2 //
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 package frontend
17
18 import (
19         "io/fs"
20         "testing"
21
22         "ruderich.org/simon/safcm"
23         "ruderich.org/simon/safcm/testutil"
24 )
25
26 func TestFormatChanges(t *testing.T) {
27         tests := []struct {
28                 name   string
29                 dryRun bool
30                 quiet  bool
31                 isTTY  bool
32                 resp   safcm.MsgSyncResp
33                 exp    string
34         }{
35
36                 // Just a few basic tests and border cases; see the other
37                 // tests for more detailed tests of each format function
38
39                 {
40                         "no changes",
41                         false,
42                         false,
43                         false,
44                         safcm.MsgSyncResp{},
45                         "no changes",
46                 },
47
48                 {
49                         "changes",
50                         false,
51                         false,
52                         false,
53                         safcm.MsgSyncResp{
54                                 FileChanges: []safcm.FileChange{
55                                         {
56                                                 Path:    "created",
57                                                 Created: true,
58                                                 New: safcm.FileChangeInfo{
59                                                         Mode:  0644,
60                                                         User:  "user",
61                                                         Uid:   1000,
62                                                         Group: "group",
63                                                         Gid:   2000,
64                                                 },
65                                         },
66                                 },
67                                 PackageChanges: []safcm.PackageChange{
68                                         {
69                                                 Name: "package-one",
70                                         },
71                                         {
72                                                 Name: "package-two",
73                                         },
74                                 },
75                                 ServiceChanges: []safcm.ServiceChange{
76                                         {
77                                                 Name:    "service-one",
78                                                 Started: true,
79                                         },
80                                         {
81                                                 Name:    "service-two",
82                                                 Enabled: true,
83                                         },
84                                         {
85                                                 Name:    "service-three",
86                                                 Started: true,
87                                                 Enabled: true,
88                                         },
89                                 },
90                                 CommandChanges: []safcm.CommandChange{
91                                         {
92                                                 Command: "fake command",
93                                                 Output:  "fake output",
94                                         },
95                                         {
96                                                 Command: "fake command with no output",
97                                         },
98                                 },
99                         },
100                         "\nchanged 1 file(s):\n\"created\": created, file, user(1000) group(2000), 0644\n\ninstalled 2 package(s):\n\"package-one\"\n\"package-two\"\n\nmodified 3 service(s):\n\"service-one\": started\n\"service-two\": enabled\n\"service-three\": started, enabled\n\nexecuted 2 command(s):\n\"fake command\":\n   > fake output\n   > \\ No newline at end of file\n\"fake command with no output\"\n",
101                 },
102
103                 {
104                         "command changes only, dry-run",
105                         true,
106                         false,
107                         false,
108                         safcm.MsgSyncResp{
109                                 CommandChanges: []safcm.CommandChange{
110                                         {
111                                                 Command: "fake command",
112                                         },
113                                         {
114                                                 Command: "fake command with no output",
115                                         },
116                                         {
117                                                 Command: "fake command with newline",
118                                         },
119                                         {
120                                                 Command: "fake command with more output",
121                                         },
122                                         {
123                                                 Command: "fake failed command",
124                                         },
125                                 },
126                         },
127                         "\nwill execute 5 command(s): (dry-run)\n\"fake command\"\n\"fake command with no output\"\n\"fake command with newline\"\n\"fake command with more output\"\n\"fake failed command\"\n",
128                 },
129                 {
130                         "command changes only, quiet & dry-run",
131                         true,
132                         true,
133                         false,
134                         safcm.MsgSyncResp{
135                                 CommandChanges: []safcm.CommandChange{
136                                         {
137                                                 Command: "fake command",
138                                         },
139                                         {
140                                                 Command: "fake command with no output",
141                                         },
142                                         {
143                                                 Command: "fake command with newline",
144                                         },
145                                         {
146                                                 Command: "fake command with more output",
147                                         },
148                                         {
149                                                 Command: "fake failed command",
150                                         },
151                                 },
152                         },
153                         "will execute 5 command(s) (dry-run)\n",
154                 },
155         }
156
157         for _, tc := range tests {
158                 t.Run(tc.name, func(t *testing.T) {
159                         c := Changes{
160                                 DryRun: tc.dryRun,
161                                 Quiet:  tc.quiet,
162                                 IsTTY:  tc.isTTY,
163                         }
164
165                         res := c.FormatChanges(tc.resp)
166                         testutil.AssertEqual(t, "res", res, tc.exp)
167                 })
168         }
169 }
170
171 func TestFormatFileChanges(t *testing.T) {
172         tests := []struct {
173                 name    string
174                 dryRun  bool
175                 isTTY   bool
176                 changes []safcm.FileChange
177                 exp     string
178         }{
179
180                 {
181                         "regular",
182                         false,
183                         false,
184                         []safcm.FileChange{
185                                 {
186                                         Path:    "created: file",
187                                         Created: true,
188                                         New: safcm.FileChangeInfo{
189                                                 Mode:  0644,
190                                                 User:  "user",
191                                                 Uid:   1000,
192                                                 Group: "group",
193                                                 Gid:   2000,
194                                         },
195                                 },
196                                 {
197                                         Path:    "created: link",
198                                         Created: true,
199                                         New: safcm.FileChangeInfo{
200                                                 Mode:  fs.ModeSymlink | 0777,
201                                                 User:  "user",
202                                                 Uid:   1000,
203                                                 Group: "group",
204                                                 Gid:   2000,
205                                         },
206                                 },
207                                 {
208                                         Path: "type change: file -> dir",
209                                         Old: safcm.FileChangeInfo{
210                                                 Mode:  0751,
211                                                 User:  "user",
212                                                 Uid:   1000,
213                                                 Group: "group",
214                                                 Gid:   2000,
215                                         },
216                                         New: safcm.FileChangeInfo{
217                                                 Mode:  fs.ModeDir | 0751,
218                                                 User:  "user",
219                                                 Uid:   1000,
220                                                 Group: "group",
221                                                 Gid:   2000,
222                                         },
223                                         DataDiff: `@@ -1,2 +1 @@
224 -content
225  
226 `,
227                                 },
228                                 {
229                                         Path: "user change",
230                                         Old: safcm.FileChangeInfo{
231                                                 Mode:  0755,
232                                                 User:  "user",
233                                                 Uid:   1000,
234                                                 Group: "group",
235                                                 Gid:   2000,
236                                         },
237                                         New: safcm.FileChangeInfo{
238                                                 Mode:  0755,
239                                                 User:  "user2",
240                                                 Uid:   1001,
241                                                 Group: "group",
242                                                 Gid:   2000,
243                                         },
244                                 },
245                                 {
246                                         Path: "group change",
247                                         Old: safcm.FileChangeInfo{
248                                                 Mode:  0755,
249                                                 User:  "user",
250                                                 Uid:   1000,
251                                                 Group: "group",
252                                                 Gid:   2000,
253                                         },
254                                         New: safcm.FileChangeInfo{
255                                                 Mode:  0755,
256                                                 User:  "user",
257                                                 Uid:   1000,
258                                                 Group: "group2",
259                                                 Gid:   2001,
260                                         },
261                                 },
262                                 {
263                                         Path: "mode change",
264                                         Old: safcm.FileChangeInfo{
265                                                 Mode:  0755,
266                                                 User:  "user",
267                                                 Uid:   1000,
268                                                 Group: "group",
269                                                 Gid:   2000,
270                                         },
271                                         New: safcm.FileChangeInfo{
272                                                 Mode:  0750,
273                                                 User:  "user",
274                                                 Uid:   1000,
275                                                 Group: "group",
276                                                 Gid:   2000,
277                                         },
278                                 },
279                                 {
280                                         Path: "mode change (setuid)",
281                                         Old: safcm.FileChangeInfo{
282                                                 Mode:  0755,
283                                                 User:  "user",
284                                                 Uid:   1000,
285                                                 Group: "group",
286                                                 Gid:   2000,
287                                         },
288                                         New: safcm.FileChangeInfo{
289                                                 Mode:  0755 | fs.ModeSetuid,
290                                                 User:  "user",
291                                                 Uid:   1000,
292                                                 Group: "group",
293                                                 Gid:   2000,
294                                         },
295                                 },
296                                 {
297                                         Path: "content change",
298                                         Old: safcm.FileChangeInfo{
299                                                 Mode:  0644,
300                                                 User:  "user",
301                                                 Uid:   1000,
302                                                 Group: "group",
303                                                 Gid:   2000,
304                                         },
305                                         New: safcm.FileChangeInfo{
306                                                 Mode:  0644,
307                                                 User:  "user",
308                                                 Uid:   1000,
309                                                 Group: "group",
310                                                 Gid:   2000,
311                                         },
312                                         DataDiff: `@@ -1,2 +1,2 @@
313 -old content
314 +content
315  
316 `,
317                                 },
318                                 {
319                                         Path: "multiple changes",
320                                         Old: safcm.FileChangeInfo{
321                                                 Mode:  0644,
322                                                 User:  "user",
323                                                 Uid:   1000,
324                                                 Group: "group",
325                                                 Gid:   2000,
326                                         },
327                                         New: safcm.FileChangeInfo{
328                                                 Mode:  fs.ModeDir | 0755,
329                                                 User:  "user2",
330                                                 Uid:   1001,
331                                                 Group: "group2",
332                                                 Gid:   2001,
333                                         },
334                                         DataDiff: `@@ -1,2 +1 @@
335 -content
336  
337 `,
338                                 },
339                         },
340                         `changed 9 file(s):
341 "created: file": created, file, user(1000) group(2000), 0644
342 "created: link": created, symlink, user(1000) group(2000), 0777
343 "type change: file -> dir": file -> dir
344    @@ -1,2 +1 @@
345    -content
346     
347 "user change": user(1000) group(2000) -> user2(1001) group(2000)
348 "group change": user(1000) group(2000) -> user(1000) group2(2001)
349 "mode change": 0755 -> 0750
350 "mode change (setuid)": 0755 -> 04755
351 "content change":
352    @@ -1,2 +1,2 @@
353    -old content
354    +content
355     
356 "multiple changes": file -> dir, user(1000) group(2000) -> user2(1001) group2(2001), 0644 -> 0755
357    @@ -1,2 +1 @@
358    -content
359     
360 `,
361                 },
362
363                 {
364                         "regular (tty)",
365                         false,
366                         true,
367                         []safcm.FileChange{
368                                 {
369                                         Path:    "created: file",
370                                         Created: true,
371                                         New: safcm.FileChangeInfo{
372                                                 Mode:  0644,
373                                                 User:  "user",
374                                                 Uid:   1000,
375                                                 Group: "group",
376                                                 Gid:   2000,
377                                         },
378                                 },
379                                 {
380                                         Path:    "created: link",
381                                         Created: true,
382                                         New: safcm.FileChangeInfo{
383                                                 Mode:  fs.ModeSymlink | 0777,
384                                                 User:  "user",
385                                                 Uid:   1000,
386                                                 Group: "group",
387                                                 Gid:   2000,
388                                         },
389                                 },
390                                 {
391                                         Path: "type change: file -> dir",
392                                         Old: safcm.FileChangeInfo{
393                                                 Mode:  0751,
394                                                 User:  "user",
395                                                 Uid:   1000,
396                                                 Group: "group",
397                                                 Gid:   2000,
398                                         },
399                                         New: safcm.FileChangeInfo{
400                                                 Mode:  fs.ModeDir | 0751,
401                                                 User:  "user",
402                                                 Uid:   1000,
403                                                 Group: "group",
404                                                 Gid:   2000,
405                                         },
406                                         DataDiff: `@@ -1,2 +1 @@
407 -content
408  
409 `,
410                                 },
411                                 {
412                                         Path: "user change",
413                                         Old: safcm.FileChangeInfo{
414                                                 Mode:  0755,
415                                                 User:  "user",
416                                                 Uid:   1000,
417                                                 Group: "group",
418                                                 Gid:   2000,
419                                         },
420                                         New: safcm.FileChangeInfo{
421                                                 Mode:  0755,
422                                                 User:  "user2",
423                                                 Uid:   1001,
424                                                 Group: "group",
425                                                 Gid:   2000,
426                                         },
427                                 },
428                                 {
429                                         Path: "group change",
430                                         Old: safcm.FileChangeInfo{
431                                                 Mode:  0755,
432                                                 User:  "user",
433                                                 Uid:   1000,
434                                                 Group: "group",
435                                                 Gid:   2000,
436                                         },
437                                         New: safcm.FileChangeInfo{
438                                                 Mode:  0755,
439                                                 User:  "user",
440                                                 Uid:   1000,
441                                                 Group: "group2",
442                                                 Gid:   2001,
443                                         },
444                                 },
445                                 {
446                                         Path: "mode change",
447                                         Old: safcm.FileChangeInfo{
448                                                 Mode:  0755,
449                                                 User:  "user",
450                                                 Uid:   1000,
451                                                 Group: "group",
452                                                 Gid:   2000,
453                                         },
454                                         New: safcm.FileChangeInfo{
455                                                 Mode:  0750,
456                                                 User:  "user",
457                                                 Uid:   1000,
458                                                 Group: "group",
459                                                 Gid:   2000,
460                                         },
461                                 },
462                                 {
463                                         Path: "mode change (setuid)",
464                                         Old: safcm.FileChangeInfo{
465                                                 Mode:  0755,
466                                                 User:  "user",
467                                                 Uid:   1000,
468                                                 Group: "group",
469                                                 Gid:   2000,
470                                         },
471                                         New: safcm.FileChangeInfo{
472                                                 Mode:  0755 | fs.ModeSetuid,
473                                                 User:  "user",
474                                                 Uid:   1000,
475                                                 Group: "group",
476                                                 Gid:   2000,
477                                         },
478                                 },
479                                 {
480                                         Path: "content change",
481                                         Old: safcm.FileChangeInfo{
482                                                 Mode:  0644,
483                                                 User:  "user",
484                                                 Uid:   1000,
485                                                 Group: "group",
486                                                 Gid:   2000,
487                                         },
488                                         New: safcm.FileChangeInfo{
489                                                 Mode:  0644,
490                                                 User:  "user",
491                                                 Uid:   1000,
492                                                 Group: "group",
493                                                 Gid:   2000,
494                                         },
495                                         DataDiff: `@@ -1,2 +1,2 @@
496 -old content
497 +content
498  
499 `,
500                                 },
501                                 {
502                                         Path: "multiple changes",
503                                         Old: safcm.FileChangeInfo{
504                                                 Mode:  0644,
505                                                 User:  "user",
506                                                 Uid:   1000,
507                                                 Group: "group",
508                                                 Gid:   2000,
509                                         },
510                                         New: safcm.FileChangeInfo{
511                                                 Mode:  fs.ModeDir | 0755,
512                                                 User:  "user2",
513                                                 Uid:   1001,
514                                                 Group: "group2",
515                                                 Gid:   2001,
516                                         },
517                                         DataDiff: `@@ -1,2 +1 @@
518 -content
519  
520 `,
521                                 },
522                         },
523                         "changed 9 file(s):\n\x1b[36m\"created: file\"\x1b[0m: \x1b[32mcreated\x1b[0m, file, user(1000) group(2000), 0644\n\x1b[36m\"created: link\"\x1b[0m: \x1b[32mcreated\x1b[0m, symlink, user(1000) group(2000), 0777\n\x1b[36m\"type change: file -> dir\"\x1b[0m: file -> dir\n   @@ -1,2 +1 @@\n\x1b[31m   -content\x1b[0m\n    \n\x1b[36m\"user change\"\x1b[0m: user(1000) group(2000) -> user2(1001) group(2000)\n\x1b[36m\"group change\"\x1b[0m: user(1000) group(2000) -> user(1000) group2(2001)\n\x1b[36m\"mode change\"\x1b[0m: 0755 -> 0750\n\x1b[36m\"mode change (setuid)\"\x1b[0m: 0755 -> 04755\n\x1b[36m\"content change\"\x1b[0m:\n   @@ -1,2 +1,2 @@\n\x1b[31m   -old content\x1b[0m\n\x1b[32m   +content\x1b[0m\n    \n\x1b[36m\"multiple changes\"\x1b[0m: file -> dir, user(1000) group(2000) -> user2(1001) group2(2001), 0644 -> 0755\n   @@ -1,2 +1 @@\n\x1b[31m   -content\x1b[0m\n    \n",
524                 },
525
526                 {
527                         "dry-run",
528                         true,
529                         false,
530                         []safcm.FileChange{
531                                 {
532                                         Path:    "file",
533                                         Created: true,
534                                         New: safcm.FileChangeInfo{
535                                                 Mode:  0644,
536                                                 User:  "user",
537                                                 Uid:   1000,
538                                                 Group: "group",
539                                                 Gid:   2000,
540                                         },
541                                 },
542                         },
543                         `will change 1 file(s): (dry-run)
544 "file": created, file, user(1000) group(2000), 0644
545 `,
546                 },
547
548                 {
549                         "dry-run (tty)",
550                         true,
551                         true,
552                         []safcm.FileChange{
553                                 {
554                                         Path:    "file",
555                                         Created: true,
556                                         New: safcm.FileChangeInfo{
557                                                 Mode:  0644,
558                                                 User:  "user",
559                                                 Uid:   1000,
560                                                 Group: "group",
561                                                 Gid:   2000,
562                                         },
563                                 },
564                         },
565                         "will change 1 file(s): (dry-run)\n\x1B[36m\"file\"\x1B[0m: \x1B[32mcreated\x1B[0m, file, user(1000) group(2000), 0644\n",
566                 },
567
568                 {
569                         "escaping",
570                         false,
571                         false,
572                         []safcm.FileChange{
573                                 {
574                                         Path:    "\x00",
575                                         Created: true,
576                                         New: safcm.FileChangeInfo{
577                                                 Mode:  0xFFFFFFFF,
578                                                 User:  "\x01",
579                                                 Uid:   -1,
580                                                 Group: "\x02",
581                                                 Gid:   -2,
582                                         },
583                                         DataDiff: "\x03",
584                                 },
585                                 {
586                                         Path: "\x00",
587                                         Old: safcm.FileChangeInfo{
588                                                 Mode:  0x00000000,
589                                                 User:  "\x01",
590                                                 Uid:   -1,
591                                                 Group: "\x02",
592                                                 Gid:   -2,
593                                         },
594                                         New: safcm.FileChangeInfo{
595                                                 Mode:  0xFFFFFFFF,
596                                                 User:  "\x03",
597                                                 Uid:   -3,
598                                                 Group: "\x04",
599                                                 Gid:   -4,
600                                         },
601                                         DataDiff: "\x05",
602                                 },
603                         },
604                         `changed 2 file(s):
605 "\x00": created, invalid type dLDpSc?---------, \x01(-1) \x02(-2), 07777
606    \x03
607    \ No newline at end of file
608 "\x00": file -> invalid type dLDpSc?---------, \x01(-1) \x02(-2) -> \x03(-3) \x04(-4), 0 -> 07777
609    \x05
610    \ No newline at end of file
611 `,
612                 },
613
614                 {
615                         "escaping (tty)",
616                         false,
617                         true,
618                         []safcm.FileChange{
619                                 {
620                                         Path:    "\x00",
621                                         Created: true,
622                                         New: safcm.FileChangeInfo{
623                                                 Mode:  0xFFFFFFFF,
624                                                 User:  "\x01",
625                                                 Uid:   -1,
626                                                 Group: "\x02",
627                                                 Gid:   -2,
628                                         },
629                                         DataDiff: "\x03",
630                                 },
631                                 {
632                                         Path: "\x00",
633                                         Old: safcm.FileChangeInfo{
634                                                 Mode:  0x00000000,
635                                                 User:  "\x01",
636                                                 Uid:   -1,
637                                                 Group: "\x02",
638                                                 Gid:   -2,
639                                         },
640                                         New: safcm.FileChangeInfo{
641                                                 Mode:  0xFFFFFFFF,
642                                                 User:  "\x03",
643                                                 Uid:   -3,
644                                                 Group: "\x04",
645                                                 Gid:   -4,
646                                         },
647                                         DataDiff: "\x05",
648                                 },
649                         },
650                         "changed 2 file(s):\n\x1b[36m\"\\x00\"\x1b[0m: \x1b[32mcreated\x1b[0m, invalid type dLDpSc?---------, \\x01(-1) \\x02(-2), 07777\n   \\x03\n   \\ No newline at end of file\n\x1b[36m\"\\x00\"\x1b[0m: file -> invalid type dLDpSc?---------, \\x01(-1) \\x02(-2) -> \\x03(-3) \\x04(-4), 0 -> 07777\n   \\x05\n   \\ No newline at end of file\n",
651                 },
652         }
653
654         for _, tc := range tests {
655                 t.Run(tc.name, func(t *testing.T) {
656                         c := Changes{
657                                 DryRun: tc.dryRun,
658                                 IsTTY:  tc.isTTY,
659                         }
660
661                         res := c.FormatFileChanges(tc.changes)
662                         testutil.AssertEqual(t, "res", res, tc.exp)
663                 })
664         }
665 }
666
667 func TestFormatPackageChanges(t *testing.T) {
668         tests := []struct {
669                 name    string
670                 dryRun  bool
671                 isTTY   bool
672                 changes []safcm.PackageChange
673                 exp     string
674         }{
675
676                 {
677                         "regular",
678                         false,
679                         false,
680                         []safcm.PackageChange{
681                                 {
682                                         Name: "package-one",
683                                 },
684                                 {
685                                         Name: "package-two",
686                                 },
687                         },
688                         `installed 2 package(s):
689 "package-one"
690 "package-two"
691 `,
692                 },
693
694                 {
695                         "regular (tty)",
696                         false,
697                         true,
698                         []safcm.PackageChange{
699                                 {
700                                         Name: "package-one",
701                                 },
702                                 {
703                                         Name: "package-two",
704                                 },
705                         },
706                         "installed 2 package(s):\n\x1b[36m\"package-one\"\x1b[0m\n\x1b[36m\"package-two\"\x1b[0m\n",
707                 },
708
709                 {
710                         "dry-run",
711                         true,
712                         false,
713                         []safcm.PackageChange{
714                                 {
715                                         Name: "package-one",
716                                 },
717                                 {
718                                         Name: "package-two",
719                                 },
720                         },
721                         `will install 2 package(s): (dry-run)
722 "package-one"
723 "package-two"
724 `,
725                 },
726
727                 {
728                         "dry-run (tty)",
729                         true,
730                         true,
731                         []safcm.PackageChange{
732                                 {
733                                         Name: "package-one",
734                                 },
735                                 {
736                                         Name: "package-two",
737                                 },
738                         },
739                         "will install 2 package(s): (dry-run)\n\x1b[36m\"package-one\"\x1b[0m\n\x1b[36m\"package-two\"\x1b[0m\n",
740                 },
741
742                 {
743                         "escaping",
744                         false,
745                         false,
746                         []safcm.PackageChange{
747                                 {
748                                         Name: "\x00",
749                                 },
750                         },
751                         `installed 1 package(s):
752 "\x00"
753 `,
754                 },
755
756                 {
757                         "escaping (tty)",
758                         false,
759                         true,
760                         []safcm.PackageChange{
761                                 {
762                                         Name: "\x00",
763                                 },
764                         },
765                         "installed 1 package(s):\n\x1b[36m\"\\x00\"\x1b[0m\n",
766                 },
767         }
768
769         for _, tc := range tests {
770                 t.Run(tc.name, func(t *testing.T) {
771                         c := Changes{
772                                 DryRun: tc.dryRun,
773                                 IsTTY:  tc.isTTY,
774                         }
775
776                         res := c.FormatPackageChanges(tc.changes)
777                         testutil.AssertEqual(t, "res", res, tc.exp)
778                 })
779         }
780 }
781
782 func TestFormatServiceChanges(t *testing.T) {
783         tests := []struct {
784                 name    string
785                 dryRun  bool
786                 isTTY   bool
787                 changes []safcm.ServiceChange
788                 exp     string
789         }{
790
791                 {
792                         "regular",
793                         false,
794                         false,
795                         []safcm.ServiceChange{
796                                 {
797                                         Name:    "service-one",
798                                         Started: true,
799                                 },
800                                 {
801                                         Name:    "service-two",
802                                         Enabled: true,
803                                 },
804                                 {
805                                         Name:    "service-three",
806                                         Started: true,
807                                         Enabled: true,
808                                 },
809                         },
810                         `modified 3 service(s):
811 "service-one": started
812 "service-two": enabled
813 "service-three": started, enabled
814 `,
815                 },
816
817                 {
818                         "regular (tty)",
819                         false,
820                         true,
821                         []safcm.ServiceChange{
822                                 {
823                                         Name:    "service-one",
824                                         Started: true,
825                                 },
826                                 {
827                                         Name:    "service-two",
828                                         Enabled: true,
829                                 },
830                                 {
831                                         Name:    "service-three",
832                                         Started: true,
833                                         Enabled: true,
834                                 },
835                         },
836                         "modified 3 service(s):\n\x1b[36m\"service-one\"\x1b[0m: started\n\x1b[36m\"service-two\"\x1b[0m: enabled\n\x1b[36m\"service-three\"\x1b[0m: started, enabled\n",
837                 },
838
839                 {
840                         "dry-run",
841                         true,
842                         false,
843                         []safcm.ServiceChange{
844                                 {
845                                         Name:    "service-one",
846                                         Started: true,
847                                 },
848                                 {
849                                         Name:    "service-two",
850                                         Enabled: true,
851                                 },
852                                 {
853                                         Name:    "service-three",
854                                         Started: true,
855                                         Enabled: true,
856                                 },
857                         },
858                         `will modify 3 service(s): (dry-run)
859 "service-one": started
860 "service-two": enabled
861 "service-three": started, enabled
862 `,
863                 },
864
865                 {
866                         "dry-run (tty)",
867                         true,
868                         true,
869                         []safcm.ServiceChange{
870                                 {
871                                         Name:    "service-one",
872                                         Started: true,
873                                 },
874                                 {
875                                         Name:    "service-two",
876                                         Enabled: true,
877                                 },
878                                 {
879                                         Name:    "service-three",
880                                         Started: true,
881                                         Enabled: true,
882                                 },
883                         },
884                         "will modify 3 service(s): (dry-run)\n\x1b[36m\"service-one\"\x1b[0m: started\n\x1b[36m\"service-two\"\x1b[0m: enabled\n\x1b[36m\"service-three\"\x1b[0m: started, enabled\n",
885                 },
886
887                 {
888                         "escaping",
889                         false,
890                         false,
891                         []safcm.ServiceChange{
892                                 {
893                                         Name: "\x00",
894                                 },
895                                 {
896                                         Name:    "\x01",
897                                         Started: true,
898                                         Enabled: true,
899                                 },
900                         },
901                         `modified 2 service(s):
902 "\x00": 
903 "\x01": started, enabled
904 `,
905                 },
906
907                 {
908                         "escaping (tty)",
909                         false,
910                         true,
911                         []safcm.ServiceChange{
912                                 {
913                                         Name: "\x00",
914                                 },
915                                 {
916                                         Name:    "\x01",
917                                         Started: true,
918                                         Enabled: true,
919                                 },
920                         },
921                         "modified 2 service(s):\n\x1b[36m\"\\x00\"\x1b[0m: \n\x1b[36m\"\\x01\"\x1b[0m: started, enabled\n",
922                 },
923         }
924
925         for _, tc := range tests {
926                 t.Run(tc.name, func(t *testing.T) {
927                         c := Changes{
928                                 DryRun: tc.dryRun,
929                                 IsTTY:  tc.isTTY,
930                         }
931
932                         res := c.FormatServiceChanges(tc.changes)
933                         testutil.AssertEqual(t, "res", res, tc.exp)
934                 })
935         }
936 }
937
938 func TestFormatCommandChanges(t *testing.T) {
939         tests := []struct {
940                 name    string
941                 dryRun  bool
942                 quiet   bool
943                 isTTY   bool
944                 changes []safcm.CommandChange
945                 exp     string
946         }{
947
948                 {
949                         "regular",
950                         false,
951                         false,
952                         false,
953                         []safcm.CommandChange{
954                                 {
955                                         Command: "fake command",
956                                         Output:  "fake output",
957                                 },
958                                 {
959                                         Command: "fake command with no output",
960                                 },
961                                 {
962                                         Command: "fake command with newline",
963                                         Output:  "fake output\n",
964                                 },
965                                 {
966                                         Command: "fake command with more output",
967                                         Output:  "fake out\nfake put\nfake\n",
968                                 },
969                                 {
970                                         Command: "fake failed command",
971                                         Output:  "fake output",
972                                         Error:   "fake error",
973                                 },
974                         },
975                         `executed 5 command(s):
976 "fake command":
977    > fake output
978    > \ No newline at end of file
979 "fake command with no output"
980 "fake command with newline":
981    > fake output
982 "fake command with more output":
983    > fake out
984    > fake put
985    > fake
986 "fake failed command", failed: "fake error":
987    > fake output
988    > \ No newline at end of file
989 `,
990                 },
991
992                 {
993                         "regular (tty)",
994                         false,
995                         false,
996                         true,
997                         []safcm.CommandChange{
998                                 {
999                                         Command: "fake command",
1000                                         Output:  "fake output",
1001                                 },
1002                                 {
1003                                         Command: "fake command with no output",
1004                                 },
1005                                 {
1006                                         Command: "fake command with newline",
1007                                         Output:  "fake output\n",
1008                                 },
1009                                 {
1010                                         Command: "fake command with more output",
1011                                         Output:  "fake out\nfake put\nfake\n",
1012                                 },
1013                                 {
1014                                         Command: "fake failed command",
1015                                         Output:  "fake output",
1016                                         Error:   "fake error",
1017                                 },
1018                         },
1019                         "executed 5 command(s):\n\x1b[36m\"fake command\"\x1b[0m:\n   > fake output\n   > \\ No newline at end of file\n\x1b[36m\"fake command with no output\"\x1b[0m\n\x1b[36m\"fake command with newline\"\x1b[0m:\n   > fake output\n\x1b[36m\"fake command with more output\"\x1b[0m:\n   > fake out\n   > fake put\n   > fake\n\x1b[36m\"fake failed command\"\x1b[0m, failed: \"fake error\":\n   > fake output\n   > \\ No newline at end of file\n",
1020                 },
1021
1022                 {
1023                         "dry-run",
1024                         true,
1025                         false,
1026                         false,
1027                         []safcm.CommandChange{
1028                                 {
1029                                         Command: "fake command",
1030                                 },
1031                         },
1032                         `will execute 1 command(s): (dry-run)
1033 "fake command"
1034 `,
1035                 },
1036
1037                 {
1038                         "dry-run (tty)",
1039                         true,
1040                         false,
1041                         true,
1042                         []safcm.CommandChange{
1043                                 {
1044                                         Command: "fake command",
1045                                 },
1046                         },
1047                         "will execute 1 command(s): (dry-run)\n\x1b[36m\"fake command\"\x1b[0m\n",
1048                 },
1049
1050                 {
1051                         "quiet",
1052                         false,
1053                         true,
1054                         false,
1055                         []safcm.CommandChange{
1056                                 {
1057                                         Command: "fake command",
1058                                         Output:  "fake output",
1059                                 },
1060                                 {
1061                                         Command: "fake command with no output",
1062                                 },
1063                                 {
1064                                         Command: "fake command with newline",
1065                                         Output:  "fake output\n",
1066                                 },
1067                                 {
1068                                         Command: "fake command with more output",
1069                                         Output:  "fake out\nfake put\nfake\n",
1070                                 },
1071                                 {
1072                                         Command: "fake failed command",
1073                                         Output:  "fake output",
1074                                         Error:   "fake error",
1075                                 },
1076                         },
1077                         `executed 5 command(s), 1 with no output (hidden):
1078 "fake command":
1079    > fake output
1080    > \ No newline at end of file
1081 "fake command with newline":
1082    > fake output
1083 "fake command with more output":
1084    > fake out
1085    > fake put
1086    > fake
1087 "fake failed command", failed: "fake error":
1088    > fake output
1089    > \ No newline at end of file
1090 `,
1091                 },
1092
1093                 {
1094                         "quiet (tty)",
1095                         false,
1096                         true,
1097                         true,
1098                         []safcm.CommandChange{
1099                                 {
1100                                         Command: "fake command",
1101                                         Output:  "fake output",
1102                                 },
1103                                 {
1104                                         Command: "fake command with no output",
1105                                 },
1106                                 {
1107                                         Command: "fake command with newline",
1108                                         Output:  "fake output\n",
1109                                 },
1110                                 {
1111                                         Command: "fake command with more output",
1112                                         Output:  "fake out\nfake put\nfake\n",
1113                                 },
1114                                 {
1115                                         Command: "fake failed command",
1116                                         Output:  "fake output",
1117                                         Error:   "fake error",
1118                                 },
1119                         },
1120                         "executed 5 command(s), 1 with no output (hidden):\n\x1b[36m\"fake command\"\x1b[0m:\n   > fake output\n   > \\ No newline at end of file\n\x1b[36m\"fake command with newline\"\x1b[0m:\n   > fake output\n\x1b[36m\"fake command with more output\"\x1b[0m:\n   > fake out\n   > fake put\n   > fake\n\x1b[36m\"fake failed command\"\x1b[0m, failed: \"fake error\":\n   > fake output\n   > \\ No newline at end of file\n",
1121                 },
1122
1123                 {
1124                         "quiet (only quiet commands)",
1125                         false,
1126                         true,
1127                         false,
1128                         []safcm.CommandChange{
1129                                 {
1130                                         Command: "fake command with no output",
1131                                 },
1132                                 {
1133                                         Command: "fake command with no output",
1134                                 },
1135                         },
1136                         `executed 2 command(s), 2 with no output (hidden)
1137 `,
1138                 },
1139
1140                 {
1141                         "quiet (quiet with errors)",
1142                         false,
1143                         true,
1144                         false,
1145                         []safcm.CommandChange{
1146                                 {
1147                                         Command: "fake command with no output but error",
1148                                         Error:   "fake error",
1149                                 },
1150                                 {
1151                                         Command: "fake command with no output",
1152                                 },
1153                         },
1154                         `executed 2 command(s), 1 with no output (hidden):
1155 "fake command with no output but error", failed: "fake error"
1156 `,
1157                 },
1158
1159                 {
1160                         "quiet & dry-run",
1161                         true,
1162                         true,
1163                         false,
1164                         []safcm.CommandChange{
1165                                 {
1166                                         Command: "fake command",
1167                                 },
1168                                 {
1169                                         Command: "fake command with no output",
1170                                 },
1171                                 {
1172                                         Command: "fake command with newline",
1173                                 },
1174                                 {
1175                                         Command: "fake command with more output",
1176                                 },
1177                                 {
1178                                         Command: "fake failed command",
1179                                 },
1180                         },
1181                         `will execute 5 command(s) (dry-run)
1182 `,
1183                 },
1184
1185                 {
1186                         "escaping",
1187                         false,
1188                         false,
1189                         false,
1190                         []safcm.CommandChange{
1191                                 {
1192                                         Command: "\x00",
1193                                         Trigger: "\x01",
1194                                         Output:  "\x02",
1195                                         Error:   "\x03",
1196                                 },
1197                         },
1198                         `executed 1 command(s):
1199 "\x00", trigger for "\x01", failed: "\x03":
1200    > \x02
1201    > \ No newline at end of file
1202 `,
1203                 },
1204
1205                 {
1206                         "escaping (tty)",
1207                         false,
1208                         false,
1209                         true,
1210                         []safcm.CommandChange{
1211                                 {
1212                                         Command: "\x00",
1213                                         Trigger: "\x01",
1214                                         Output:  "\x02",
1215                                         Error:   "\x03",
1216                                 },
1217                         },
1218                         "executed 1 command(s):\n\x1b[36m\"\\x00\"\x1b[0m, trigger for \"\\x01\", failed: \"\\x03\":\n   > \x1b[35m\\x02\x1b[0m\n   > \\ No newline at end of file\n",
1219                 },
1220         }
1221
1222         for _, tc := range tests {
1223                 t.Run(tc.name, func(t *testing.T) {
1224                         c := Changes{
1225                                 DryRun: tc.dryRun,
1226                                 Quiet:  tc.quiet,
1227                                 IsTTY:  tc.isTTY,
1228                         }
1229
1230                         res := c.FormatCommandChanges(tc.changes)
1231                         testutil.AssertEqual(t, "res", res, tc.exp)
1232                 })
1233         }
1234 }