package host import ( "context" "os" "strings" "testing" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/rootstate" ) func TestPlan14UnitPredecessorHandover(t *testing.T) { for _, attack := range []string{"none", "not retired", "same owner", "shape", "replacement", "lost materialization"} { t.Run(attack, func(t *testing.T) { b, in, shape := plan14UnitBindingFixture(t, "console.service", "", "") shape.Program = strings.Repeat("a", 64) b.canonical = func(context.Context, string) (claimUnitShape, error) { return shape, nil } b.installation = func(context.Context, string) (hostguard.RootInstallation, error) { return in, nil } files := &plan14UnitFiles{} state := &plan14UnitBindingFault{Store: &rollbackMemory{files: map[string][]byte{}}} m := claimUnitMaterializer{idle: func(string) error { return nil }, state: state, read: files.read, create: files.create, replace: files.replace} b.handover = m.handover old, err := b.establish(t.Context(), in.Host) if err != nil { t.Fatal(err) } if err = m.install(t.Context(), old); err != nil { t.Fatal(err) } if attack != "not retired" { if err = b.retire(t.Context(), old, func(claimUnitBinding) error { return m.retire(t.Context(), old, func() error { return nil }) }); err != nil { t.Fatal(err) } } if attack != "same owner" { in.Stamp++ } if attack == "shape" { shape.Addr = "127.0.0.1:9999" } if attack == "replacement" { files.id.Inode++ } original := m.replace if attack == "lost materialization" { m.replace = func(unit string, id rootstate.FileIdentity, body []byte) error { if err := original(unit, id, body); err != nil { return err } return os.ErrInvalid } b.handover = m.handover } next, err := b.establish(t.Context(), in.Host) if attack != "none" && attack != "lost materialization" { if err == nil { t.Fatal("unsafe handover accepted", next) } return } if attack == "lost materialization" { if err == nil { t.Fatal("lost replacement reported success") } m.replace = original b.handover = m.handover next, err = b.establish(t.Context(), in.Host) } if err != nil { t.Fatal(err) } if next.Owner.Stamp != in.Stamp || next.Retired || next.Retiring { t.Fatal(next) } if err = m.verify(next); err != nil { t.Fatal(err) } if err = b.retire(t.Context(), old, func(claimUnitBinding) error { t.Fatal("predecessor reached successor"); return nil }); err == nil { t.Fatal("stale retirement accepted") } if _, err = b.establish(t.Context(), in.Host); err != nil { t.Fatal("replay", err) } }) } }