106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package client
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/m3tam3re/agent-lib/internal/gitsource"
|
||
|
|
"github.com/m3tam3re/agent-lib/internal/lockfile"
|
||
|
|
)
|
||
|
|
|
||
|
|
type countingNotifier struct {
|
||
|
|
calls int
|
||
|
|
titles []string
|
||
|
|
failWith error
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *countingNotifier) Send(title, body string) error {
|
||
|
|
c.calls++
|
||
|
|
c.titles = append(c.titles, title)
|
||
|
|
return c.failWith
|
||
|
|
}
|
||
|
|
|
||
|
|
func skipPlanEntry(t *testing.T) PlanEntry {
|
||
|
|
return PlanEntry{
|
||
|
|
Action: ActSkip, Type: "skills", Name: "alpha",
|
||
|
|
RelPath: "skills/alpha", DstPath: filepath.Join(t.TempDir(), "skills", "alpha"),
|
||
|
|
NewHash: "sha256:x", Reason: "locally modified; keeping the local version (upstream changed)",
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func executeForTest(t *testing.T, plan []PlanEntry) (*SyncReport, error) {
|
||
|
|
t.Helper()
|
||
|
|
dir := t.TempDir()
|
||
|
|
paths := StatePaths{Root: dir, Manifest: filepath.Join(dir, "manifest.json"), LogFile: filepath.Join(dir, "sync.log")}
|
||
|
|
manifest := manifestWith(map[string]*ManifestItem{
|
||
|
|
"skills/alpha": {Name: "alpha", Type: "skills", Hash: "sha256:old"},
|
||
|
|
})
|
||
|
|
var tree *gitsource.GitTree
|
||
|
|
return ExecutePlan(tree, plan, manifest, paths, "rev123", os.Stdout)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestToastFiredOnceForWarnings(t *testing.T) {
|
||
|
|
orig := Notifier
|
||
|
|
t.Cleanup(func() { Notifier = orig })
|
||
|
|
fake := &countingNotifier{}
|
||
|
|
Notifier = fake
|
||
|
|
|
||
|
|
_, err := executeForTest(t, []PlanEntry{skipPlanEntry(t), {
|
||
|
|
Action: ActLeave, Type: "skills", Name: "mine", Reason: "unmanaged local item; left untouched",
|
||
|
|
}})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if fake.calls != 1 {
|
||
|
|
t.Errorf("exactly one toast per sync with warnings, got %d", fake.calls)
|
||
|
|
}
|
||
|
|
if len(fake.titles) != 1 || fake.titles[0] != "agent-lib sync" {
|
||
|
|
t.Errorf("unexpected toast titles: %v", fake.titles)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestNoToastForRoutineSync(t *testing.T) {
|
||
|
|
orig := Notifier
|
||
|
|
t.Cleanup(func() { Notifier = orig })
|
||
|
|
fake := &countingNotifier{}
|
||
|
|
Notifier = fake
|
||
|
|
|
||
|
|
_, err := executeForTest(t, []PlanEntry{{
|
||
|
|
Action: ActKeep, Type: "skills", Name: "alpha",
|
||
|
|
RelPath: "skills/alpha", DstPath: filepath.Join(t.TempDir(), "skills", "alpha"),
|
||
|
|
NewHash: "sha256:x",
|
||
|
|
}})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if fake.calls != 0 {
|
||
|
|
t.Errorf("routine sync must stay silent, got %d toasts", fake.calls)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestToastFailureNeverFailsSync(t *testing.T) {
|
||
|
|
orig := Notifier
|
||
|
|
t.Cleanup(func() { Notifier = orig })
|
||
|
|
Notifier = &countingNotifier{failWith: errors.New("no interactive session")}
|
||
|
|
|
||
|
|
report, err := executeForTest(t, []PlanEntry{skipPlanEntry(t)})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("toast failure must not fail the sync: %v", err)
|
||
|
|
}
|
||
|
|
if report.Skipped != 1 {
|
||
|
|
t.Errorf("skip must still be applied: %+v", report)
|
||
|
|
}
|
||
|
|
if _, err := os.Stat(filepath.Join(t.TempDir())); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPlanEntryFieldCoverage(t *testing.T) {
|
||
|
|
e := skipPlanEntry(t)
|
||
|
|
if e.Action != ActSkip || e.Type != lockfile.TypeSkill || e.Reason == "" {
|
||
|
|
t.Fatalf("plan entry incomplete: %+v", e)
|
||
|
|
}
|
||
|
|
}
|