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