// Copyright (C) 2021 Simon Ruderich // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package main import ( "io/fs" "testing" "github.com/google/go-cmp/cmp" "ruderich.org/simon/safcm" "ruderich.org/simon/safcm/cmd/safcm/config" ) func TestFormatFileChanges(t *testing.T) { tests := []struct { name string dryRun bool changes []safcm.FileChange exp string }{ { "regular", false, []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): "created: file": created, file, user(1000) group(2000), 0644 "created: link": created, symlink, user(1000) group(2000), 0777 "type change: file -> dir": file -> dir @@ -1,2 +1 @@ -content "user change": user(1000) group(2000) -> user2(1001) group(2000) "group change": user(1000) group(2000) -> user(1000) group2(2001) "mode change": 0755 -> 0750 "mode change (setuid)": 0755 -> 04755 "content change": @@ -1,2 +1,2 @@ -old content +content "multiple changes": file -> dir, user(1000) group(2000) -> user2(1001) group2(2001), 0644 -> 0755 @@ -1,2 +1 @@ -content `, }, { "dry-run", true, []safcm.FileChange{ { Path: "file", Created: true, New: safcm.FileChangeInfo{ Mode: 0644, User: "user", Uid: 1000, Group: "group", Gid: 2000, }, }, }, `changed 1 file(s): (dry-run) "file": created, file, user(1000) group(2000), 0644 `, }, { "escaping", false, []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): "\x00": created, invalid type dLDpSc?---------, \x01(-1) \x02(-2), 07777 \x03 \ No newline at end of file "\x00": file -> invalid type dLDpSc?---------, \x01(-1) \x02(-2) -> \x03(-3) \x04(-4), 0 -> 07777 \x05 \ 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 exp string }{ { "regular", false, []safcm.PackageChange{ { Name: "package-one", }, { Name: "package-two", }, }, `installed 2 package(s): "package-one" "package-two" `, }, { "dry-run", true, []safcm.PackageChange{ { Name: "package-one", }, { Name: "package-two", }, }, `installed 2 package(s): (dry-run) "package-one" "package-two" `, }, { "escaping", false, []safcm.PackageChange{ { Name: "\x00", }, }, `installed 1 package(s): "\x00" `, }, } for _, tc := range tests { s := &Sync{ config: &config.Config{ DryRun: tc.dryRun, }, } res := s.formatPackageChanges(tc.changes) if tc.exp != res { t.Errorf("%s: res: %s", tc.name, cmp.Diff(tc.exp, res)) } } } func TestFormatServiceChanges(t *testing.T) { tests := []struct { name string dryRun bool changes []safcm.ServiceChange exp string }{ { "regular", false, []safcm.ServiceChange{ { Name: "service-one", Started: true, }, { Name: "service-two", Enabled: true, }, { Name: "service-three", Started: true, Enabled: true, }, }, `modified 3 service(s): "service-one": started "service-two": enabled "service-three": started, enabled `, }, { "dry-run", true, []safcm.ServiceChange{ { Name: "service-one", Started: true, }, { Name: "service-two", Enabled: true, }, { Name: "service-three", Started: true, Enabled: true, }, }, `modified 3 service(s): (dry-run) "service-one": started "service-two": enabled "service-three": started, enabled `, }, { "escaping", false, []safcm.ServiceChange{ { Name: "\x00", }, { Name: "\x01", Started: true, Enabled: true, }, }, `modified 2 service(s): "\x00": "\x01": started, enabled `, }, } for _, tc := range tests { s := &Sync{ config: &config.Config{ DryRun: tc.dryRun, }, } res := s.formatServiceChanges(tc.changes) if tc.exp != res { t.Errorf("%s: res: %s", tc.name, cmp.Diff(tc.exp, res)) } } } func TestFormatCommandChanges(t *testing.T) { tests := []struct { name string dryRun bool quiet bool changes []safcm.CommandChange exp string }{ { "regular", false, 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 5 command(s): "fake command": > fake output > \ No newline at end of file "fake command with no output" "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 `, }, { "dry-run", true, false, []safcm.CommandChange{ { Command: "fake command", Output: "fake output", }, }, `executed 1 command(s): (dry-run) "fake command": > fake output > \ No newline at end of file `, }, { "quiet", 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), 1 with no output: "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 (only quiet commands)", false, true, []safcm.CommandChange{ { Command: "fake command with no output", }, { Command: "fake command with no output", }, }, `executed 2 command(s), 2 with no output `, }, { "quiet (quiet with errors)", false, true, []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: "fake command with no output but error", failed: "fake error" `, }, { "quiet & dry-run", true, true, []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", }, }, `executed 5 command(s): (dry-run) "fake command" "fake command with no output" "fake command with newline" "fake command with more output" "fake failed command" `, }, { "escaping", false, false, []safcm.CommandChange{ { Command: "\x00", Trigger: "\x01", Output: "\x02", Error: "\x03", }, }, `executed 1 command(s): "\x00", trigger for "\x01", failed: "\x03": > \x02 > \ No newline at end of file `, }, } for _, tc := range tests { s := &Sync{ config: &config.Config{ DryRun: tc.dryRun, Quiet: tc.quiet, }, } res := s.formatCommandChanges(tc.changes) if tc.exp != res { t.Errorf("%s: res: %s", tc.name, cmp.Diff(tc.exp, res)) } } }