repo-sync/sync_cmd_test.go
James Coleman 5af1dd8928
Some checks failed
Go package / build (push) Has been cancelled
first commit
2026-07-28 19:22:20 -05:00

166 lines
4.9 KiB
Go

package main
import (
"bytes"
"strings"
"testing"
"github.com/grmrgecko/repo-sync/fetch"
"github.com/grmrgecko/repo-sync/mirror"
)
// summaryFields parses a printed summary back into its fields.
func summaryFields(t *testing.T, out string) map[string]string {
t.Helper()
fields := make(map[string]string)
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
if line == "" {
continue
}
name, value, ok := strings.Cut(line, ": ")
if !ok {
t.Fatalf("summary line %q is not a field", line)
}
fields[name] = value
}
return fields
}
// TestPrintSummary verifies the printed summary reports what a calling script
// keys off, and that a dry run never reports a change.
func TestPrintSummary(t *testing.T) {
tests := []struct {
name string
summary *mirror.Summary
want map[string]string
absent []string
}{
{
name: "a run that transferred files reports a change",
summary: &mirror.Summary{
Repositories: 2,
Fetched: 7,
FetchedBytes: 4096,
Unchanged: 31,
Pruned: 2,
},
want: map[string]string{
"Number of repositories": "2",
"Number of failed repositories": "0",
"Number of files transferred": "7",
"Number of files pruned": "2",
"Total transferred file size": "4096 bytes",
"Number of files unchanged": "31",
"Repository changed": "true",
},
absent: []string{"Dry run", "Number of files to transfer"},
},
{
name: "an idle run reports no change",
summary: &mirror.Summary{Repositories: 1, Unchanged: 12},
want: map[string]string{
"Number of files transferred": "0",
"Number of files pruned": "0",
"Repository changed": "false",
},
},
{
name: "pruning alone is a change",
summary: &mirror.Summary{Repositories: 1, Pruned: 1},
want: map[string]string{
"Number of files pruned": "1",
"Repository changed": "true",
},
},
{
name: "a failed run still reports its totals",
summary: &mirror.Summary{Repositories: 3, Failed: 1, Fetched: 4},
want: map[string]string{
"Number of repositories": "3",
"Number of failed repositories": "1",
"Repository changed": "true",
},
},
{
name: "a dry run reports the work it planned but no change",
summary: &mirror.Summary{
Repositories: 1,
DryRun: true,
Fetched: 4,
Planned: 9,
PlannedBytes: 8192,
},
want: map[string]string{
"Dry run": "true",
"Number of files to transfer": "9",
"Total file size to transfer": "8192 bytes",
"Repository changed": "false",
},
// The metadata a dry run fetches must never be reported as
// files it transferred.
absent: []string{"Number of files transferred", "Number of files pruned"},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var out bytes.Buffer
printSummary(&out, tc.summary)
got := summaryFields(t, out.String())
for name, want := range tc.want {
if got[name] != want {
t.Errorf("%s = %q, want %q", name, got[name], want)
}
}
for _, name := range tc.absent {
if _, ok := got[name]; ok {
t.Errorf("%s was reported when it does not apply", name)
}
}
})
}
}
// TestPrintSummaryNone verifies a run with no summary to report prints
// nothing, rather than an empty set of fields a script would misread.
func TestPrintSummaryNone(t *testing.T) {
var out bytes.Buffer
printSummary(&out, nil)
if out.Len() != 0 {
t.Errorf("printed %q without a summary", out.String())
}
}
// TestSyncArgsMissing verifies missing file handling falls back to the
// crawler configuration and that the flags override it.
func TestSyncArgsMissing(t *testing.T) {
dest := t.TempDir()
// An unset flag pair takes the configured defaults, which is what a run
// without a config file gets from the built-in ones.
args := SyncArgs{Args: []string{"https://example.com/repo", dest}, MissingRetries: -1}
opts, err := args.options(nil)
if err != nil {
t.Fatal(err)
}
if opts.Missing != fetch.MissingRetry || opts.MissingRetries != 3 {
t.Errorf("missing = %q after %d runs, want retry after 3", opts.Missing, opts.MissingRetries)
}
// Explicit flags win, including a zero retry budget, which accepts an
// absence the first time it is seen.
args = SyncArgs{Args: []string{"https://example.com/repo", dest}, Missing: "ignore", MissingRetries: 0}
opts, err = args.options(nil)
if err != nil {
t.Fatal(err)
}
if opts.Missing != fetch.MissingIgnore || opts.MissingRetries != 0 {
t.Errorf("missing = %q after %d runs, want ignore after 0", opts.Missing, opts.MissingRetries)
}
// An unsupported mode is rejected rather than silently ignored.
args = SyncArgs{Args: []string{"https://example.com/repo", dest}, Missing: "skip", MissingRetries: -1}
if _, err := args.options(nil); err == nil {
t.Error("an unsupported missing mode was accepted")
}
}