X-Git-Url: https://ruderich.org/simon/gitweb/?a=blobdiff_plain;f=cmd%2Fsafcm%2Fsync_changes_test.go;h=219373c9522e4edfba458d3dd410b306c7ccdb06;hb=6f7b878074e898edc4942117d590f51d79a4d009;hp=76a016834b87d8a09d68547bcea26720511b13a4;hpb=f2f2bc47e8729548f3c10117f7f008b547c4afc5;p=safcm%2Fsafcm.git diff --git a/cmd/safcm/sync_changes_test.go b/cmd/safcm/sync_changes_test.go index 76a0168..219373c 100644 --- a/cmd/safcm/sync_changes_test.go +++ b/cmd/safcm/sync_changes_test.go @@ -19,16 +19,163 @@ import ( "io/fs" "testing" - "github.com/google/go-cmp/cmp" - "ruderich.org/simon/safcm" "ruderich.org/simon/safcm/cmd/safcm/config" + "ruderich.org/simon/safcm/testutil" ) +func TestFormatChanges(t *testing.T) { + tests := []struct { + name string + dryRun bool + quiet bool + isTTY bool + resp safcm.MsgSyncResp + exp string + }{ + + // Just a few basic tests and border cases; see the other + // tests for more detailed tests of each format function + + { + "no changes", + false, + false, + false, + safcm.MsgSyncResp{}, + "no changes", + }, + + { + "changes", + false, + false, + false, + safcm.MsgSyncResp{ + FileChanges: []safcm.FileChange{ + { + Path: "created", + Created: true, + New: safcm.FileChangeInfo{ + Mode: 0644, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + }, + }, + PackageChanges: []safcm.PackageChange{ + { + Name: "package-one", + }, + { + Name: "package-two", + }, + }, + ServiceChanges: []safcm.ServiceChange{ + { + Name: "service-one", + Started: true, + }, + { + Name: "service-two", + Enabled: true, + }, + { + Name: "service-three", + Started: true, + Enabled: true, + }, + }, + CommandChanges: []safcm.CommandChange{ + { + Command: "fake command", + Output: "fake output", + }, + { + Command: "fake command with no output", + }, + }, + }, + "\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", + }, + + { + "command changes only, dry-run", + true, + false, + false, + safcm.MsgSyncResp{ + CommandChanges: []safcm.CommandChange{ + { + Command: "fake command", + }, + { + Command: "fake command with no output", + }, + { + Command: "fake command with newline", + }, + { + Command: "fake command with more output", + }, + { + Command: "fake failed command", + }, + }, + }, + "\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", + }, + { + "command changes only, quiet & dry-run", + true, + true, + false, + safcm.MsgSyncResp{ + CommandChanges: []safcm.CommandChange{ + { + Command: "fake command", + }, + { + Command: "fake command with no output", + }, + { + Command: "fake command with newline", + }, + { + Command: "fake command with more output", + }, + { + Command: "fake failed command", + }, + }, + }, + "will execute 5 command(s) (dry-run)\n", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + s := &Sync{ + config: &config.Config{ + DryRun: tc.dryRun, + Quiet: tc.quiet, + }, + isTTY: tc.isTTY, + } + + res := s.formatChanges(tc.resp) + testutil.AssertEqual(t, "res", res, tc.exp) + }) + } +} + func TestFormatFileChanges(t *testing.T) { tests := []struct { name string dryRun bool + isTTY bool changes []safcm.FileChange exp string }{ @@ -36,6 +183,7 @@ func TestFormatFileChanges(t *testing.T) { { "regular", false, + false, []safcm.FileChange{ { Path: "created: file", @@ -215,9 +363,173 @@ func TestFormatFileChanges(t *testing.T) { `, }, + { + "regular (tty)", + false, + true, + []safcm.FileChange{ + { + Path: "created: file", + Created: true, + New: safcm.FileChangeInfo{ + Mode: 0644, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + }, + { + Path: "created: link", + Created: true, + New: safcm.FileChangeInfo{ + Mode: fs.ModeSymlink | 0777, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + }, + { + Path: "type change: file -> dir", + Old: safcm.FileChangeInfo{ + Mode: 0751, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + New: safcm.FileChangeInfo{ + Mode: fs.ModeDir | 0751, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + DataDiff: `@@ -1,2 +1 @@ +-content + +`, + }, + { + Path: "user change", + Old: safcm.FileChangeInfo{ + Mode: 0755, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + New: safcm.FileChangeInfo{ + Mode: 0755, + User: "user2", + Uid: 1001, + Group: "group", + Gid: 2000, + }, + }, + { + Path: "group change", + Old: safcm.FileChangeInfo{ + Mode: 0755, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + New: safcm.FileChangeInfo{ + Mode: 0755, + User: "user", + Uid: 1000, + Group: "group2", + Gid: 2001, + }, + }, + { + Path: "mode change", + Old: safcm.FileChangeInfo{ + Mode: 0755, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + New: safcm.FileChangeInfo{ + Mode: 0750, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + }, + { + Path: "mode change (setuid)", + Old: safcm.FileChangeInfo{ + Mode: 0755, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + New: safcm.FileChangeInfo{ + Mode: 0755 | fs.ModeSetuid, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + }, + { + Path: "content change", + Old: safcm.FileChangeInfo{ + Mode: 0644, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + New: safcm.FileChangeInfo{ + Mode: 0644, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + DataDiff: `@@ -1,2 +1,2 @@ +-old content ++content + +`, + }, + { + Path: "multiple changes", + Old: safcm.FileChangeInfo{ + Mode: 0644, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + New: safcm.FileChangeInfo{ + Mode: fs.ModeDir | 0755, + User: "user2", + Uid: 1001, + Group: "group2", + Gid: 2001, + }, + DataDiff: `@@ -1,2 +1 @@ +-content + +`, + }, + }, + "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", + }, + { "dry-run", true, + false, []safcm.FileChange{ { Path: "file", @@ -231,14 +543,35 @@ func TestFormatFileChanges(t *testing.T) { }, }, }, - `changed 1 file(s): (dry-run) + `will change 1 file(s): (dry-run) "file": created, file, user(1000) group(2000), 0644 `, }, + { + "dry-run (tty)", + true, + true, + []safcm.FileChange{ + { + Path: "file", + Created: true, + New: safcm.FileChangeInfo{ + Mode: 0644, + User: "user", + Uid: 1000, + Group: "group", + Gid: 2000, + }, + }, + }, + "will change 1 file(s): (dry-run)\n\x1B[36m\"file\"\x1B[0m: \x1B[32mcreated\x1B[0m, file, user(1000) group(2000), 0644\n", + }, + { "escaping", false, + false, []safcm.FileChange{ { Path: "\x00", @@ -280,34 +613,75 @@ func TestFormatFileChanges(t *testing.T) { \ No newline at end of file `, }, - } - - for _, tc := range tests { - s := &Sync{ - config: &config.Config{ - DryRun: tc.dryRun, - }, - } - res := s.formatFileChanges(tc.changes) - if tc.exp != res { - t.Errorf("%s: res: %s", tc.name, - cmp.Diff(tc.exp, res)) - } - } -} - -func TestFormatPackageChanges(t *testing.T) { - tests := []struct { - name string - dryRun bool - changes []safcm.PackageChange + { + "escaping (tty)", + false, + true, + []safcm.FileChange{ + { + Path: "\x00", + Created: true, + New: safcm.FileChangeInfo{ + Mode: 0xFFFFFFFF, + User: "\x01", + Uid: -1, + Group: "\x02", + Gid: -2, + }, + DataDiff: "\x03", + }, + { + Path: "\x00", + Old: safcm.FileChangeInfo{ + Mode: 0x00000000, + User: "\x01", + Uid: -1, + Group: "\x02", + Gid: -2, + }, + New: safcm.FileChangeInfo{ + Mode: 0xFFFFFFFF, + User: "\x03", + Uid: -3, + Group: "\x04", + Gid: -4, + }, + DataDiff: "\x05", + }, + }, + "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", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + s := &Sync{ + config: &config.Config{ + DryRun: tc.dryRun, + }, + isTTY: tc.isTTY, + } + + res := s.formatFileChanges(tc.changes) + testutil.AssertEqual(t, "res", res, tc.exp) + }) + } +} + +func TestFormatPackageChanges(t *testing.T) { + tests := []struct { + name string + dryRun bool + isTTY bool + changes []safcm.PackageChange exp string }{ { "regular", false, + false, []safcm.PackageChange{ { Name: "package-one", @@ -322,9 +696,25 @@ func TestFormatPackageChanges(t *testing.T) { `, }, + { + "regular (tty)", + false, + true, + []safcm.PackageChange{ + { + Name: "package-one", + }, + { + Name: "package-two", + }, + }, + "installed 2 package(s):\n\x1b[36m\"package-one\"\x1b[0m\n\x1b[36m\"package-two\"\x1b[0m\n", + }, + { "dry-run", true, + false, []safcm.PackageChange{ { Name: "package-one", @@ -333,15 +723,31 @@ func TestFormatPackageChanges(t *testing.T) { Name: "package-two", }, }, - `installed 2 package(s): (dry-run) + `will install 2 package(s): (dry-run) "package-one" "package-two" `, }, + { + "dry-run (tty)", + true, + true, + []safcm.PackageChange{ + { + Name: "package-one", + }, + { + Name: "package-two", + }, + }, + "will install 2 package(s): (dry-run)\n\x1b[36m\"package-one\"\x1b[0m\n\x1b[36m\"package-two\"\x1b[0m\n", + }, + { "escaping", false, + false, []safcm.PackageChange{ { Name: "\x00", @@ -351,20 +757,32 @@ func TestFormatPackageChanges(t *testing.T) { "\x00" `, }, + + { + "escaping (tty)", + false, + true, + []safcm.PackageChange{ + { + Name: "\x00", + }, + }, + "installed 1 package(s):\n\x1b[36m\"\\x00\"\x1b[0m\n", + }, } for _, tc := range tests { - s := &Sync{ - config: &config.Config{ - DryRun: tc.dryRun, - }, - } + t.Run(tc.name, func(t *testing.T) { + s := &Sync{ + config: &config.Config{ + DryRun: tc.dryRun, + }, + isTTY: tc.isTTY, + } - res := s.formatPackageChanges(tc.changes) - if tc.exp != res { - t.Errorf("%s: res: %s", tc.name, - cmp.Diff(tc.exp, res)) - } + res := s.formatPackageChanges(tc.changes) + testutil.AssertEqual(t, "res", res, tc.exp) + }) } } @@ -372,6 +790,7 @@ func TestFormatServiceChanges(t *testing.T) { tests := []struct { name string dryRun bool + isTTY bool changes []safcm.ServiceChange exp string }{ @@ -379,6 +798,7 @@ func TestFormatServiceChanges(t *testing.T) { { "regular", false, + false, []safcm.ServiceChange{ { Name: "service-one", @@ -401,9 +821,32 @@ func TestFormatServiceChanges(t *testing.T) { `, }, + { + "regular (tty)", + false, + true, + []safcm.ServiceChange{ + { + Name: "service-one", + Started: true, + }, + { + Name: "service-two", + Enabled: true, + }, + { + Name: "service-three", + Started: true, + Enabled: true, + }, + }, + "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", + }, + { "dry-run", true, + false, []safcm.ServiceChange{ { Name: "service-one", @@ -419,16 +862,39 @@ func TestFormatServiceChanges(t *testing.T) { Enabled: true, }, }, - `modified 3 service(s): (dry-run) + `will modify 3 service(s): (dry-run) "service-one": started "service-two": enabled "service-three": started, enabled `, }, + { + "dry-run (tty)", + true, + true, + []safcm.ServiceChange{ + { + Name: "service-one", + Started: true, + }, + { + Name: "service-two", + Enabled: true, + }, + { + Name: "service-three", + Started: true, + Enabled: true, + }, + }, + "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", + }, + { "escaping", false, + false, []safcm.ServiceChange{ { Name: "\x00", @@ -444,20 +910,37 @@ func TestFormatServiceChanges(t *testing.T) { "\x01": started, enabled `, }, + + { + "escaping (tty)", + false, + true, + []safcm.ServiceChange{ + { + Name: "\x00", + }, + { + Name: "\x01", + Started: true, + Enabled: true, + }, + }, + "modified 2 service(s):\n\x1b[36m\"\\x00\"\x1b[0m: \n\x1b[36m\"\\x01\"\x1b[0m: started, enabled\n", + }, } for _, tc := range tests { - s := &Sync{ - config: &config.Config{ - DryRun: tc.dryRun, - }, - } + t.Run(tc.name, func(t *testing.T) { + s := &Sync{ + config: &config.Config{ + DryRun: tc.dryRun, + }, + isTTY: tc.isTTY, + } - res := s.formatServiceChanges(tc.changes) - if tc.exp != res { - t.Errorf("%s: res: %s", tc.name, - cmp.Diff(tc.exp, res)) - } + res := s.formatServiceChanges(tc.changes) + testutil.AssertEqual(t, "res", res, tc.exp) + }) } } @@ -465,6 +948,8 @@ func TestFormatCommandChanges(t *testing.T) { tests := []struct { name string dryRun bool + quiet bool + isTTY bool changes []safcm.CommandChange exp string }{ @@ -472,6 +957,8 @@ func TestFormatCommandChanges(t *testing.T) { { "regular", false, + false, + false, []safcm.CommandChange{ { Command: "fake command", @@ -511,25 +998,204 @@ func TestFormatCommandChanges(t *testing.T) { `, }, + { + "regular (tty)", + false, + false, + true, + []safcm.CommandChange{ + { + Command: "fake command", + Output: "fake output", + }, + { + Command: "fake command with no output", + }, + { + Command: "fake command with newline", + Output: "fake output\n", + }, + { + Command: "fake command with more output", + Output: "fake out\nfake put\nfake\n", + }, + { + Command: "fake failed command", + Output: "fake output", + Error: "fake error", + }, + }, + "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", + }, + { "dry-run", true, + false, + false, + []safcm.CommandChange{ + { + Command: "fake command", + }, + }, + `will execute 1 command(s): (dry-run) +"fake command" +`, + }, + + { + "dry-run (tty)", + true, + false, + true, + []safcm.CommandChange{ + { + Command: "fake command", + }, + }, + "will execute 1 command(s): (dry-run)\n\x1b[36m\"fake command\"\x1b[0m\n", + }, + + { + "quiet", + false, + true, + false, []safcm.CommandChange{ { Command: "fake command", Output: "fake output", }, + { + Command: "fake command with no output", + }, + { + Command: "fake command with newline", + Output: "fake output\n", + }, + { + Command: "fake command with more output", + Output: "fake out\nfake put\nfake\n", + }, + { + Command: "fake failed command", + Output: "fake output", + Error: "fake error", + }, }, - `executed 1 command(s): (dry-run) + `executed 5 command(s), 1 with no output (hidden): "fake command": > fake output > \ No newline at end of file +"fake command with newline": + > fake output +"fake command with more output": + > fake out + > fake put + > fake +"fake failed command", failed: "fake error": + > fake output + > \ No newline at end of file +`, + }, + + { + "quiet (tty)", + false, + true, + true, + []safcm.CommandChange{ + { + Command: "fake command", + Output: "fake output", + }, + { + Command: "fake command with no output", + }, + { + Command: "fake command with newline", + Output: "fake output\n", + }, + { + Command: "fake command with more output", + Output: "fake out\nfake put\nfake\n", + }, + { + Command: "fake failed command", + Output: "fake output", + Error: "fake error", + }, + }, + "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", + }, + + { + "quiet (only quiet commands)", + false, + true, + false, + []safcm.CommandChange{ + { + Command: "fake command with no output", + }, + { + Command: "fake command with no output", + }, + }, + `executed 2 command(s), 2 with no output (hidden) +`, + }, + + { + "quiet (quiet with errors)", + false, + true, + false, + []safcm.CommandChange{ + { + Command: "fake command with no output but error", + Error: "fake error", + }, + { + Command: "fake command with no output", + }, + }, + `executed 2 command(s), 1 with no output (hidden): +"fake command with no output but error", failed: "fake error" +`, + }, + + { + "quiet & dry-run", + true, + true, + false, + []safcm.CommandChange{ + { + Command: "fake command", + }, + { + Command: "fake command with no output", + }, + { + Command: "fake command with newline", + }, + { + Command: "fake command with more output", + }, + { + Command: "fake failed command", + }, + }, + `will execute 5 command(s) (dry-run) `, }, { "escaping", false, + false, + false, []safcm.CommandChange{ { Command: "\x00", @@ -544,19 +1210,36 @@ func TestFormatCommandChanges(t *testing.T) { > \ No newline at end of file `, }, + + { + "escaping (tty)", + false, + false, + true, + []safcm.CommandChange{ + { + Command: "\x00", + Trigger: "\x01", + Output: "\x02", + Error: "\x03", + }, + }, + "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", + }, } for _, tc := range tests { - s := &Sync{ - config: &config.Config{ - DryRun: tc.dryRun, - }, - } + t.Run(tc.name, func(t *testing.T) { + s := &Sync{ + config: &config.Config{ + DryRun: tc.dryRun, + Quiet: tc.quiet, + }, + isTTY: tc.isTTY, + } - res := s.formatCommandChanges(tc.changes) - if tc.exp != res { - t.Errorf("%s: res: %s", tc.name, - cmp.Diff(tc.exp, res)) - } + res := s.formatCommandChanges(tc.changes) + testutil.AssertEqual(t, "res", res, tc.exp) + }) } }