staros/hosts/hosts_test.go

733 lines
20 KiB
Go
Raw Permalink Normal View History

2020-06-08 14:52:16 +08:00
package hosts
import (
"fmt"
"os"
"path/filepath"
"strings"
2020-06-08 14:52:16 +08:00
"testing"
)
func Test_Hosts(t *testing.T) {
2024-04-10 11:19:04 +08:00
var h = NewHosts()
tmpDir := t.TempDir()
2024-04-10 11:19:04 +08:00
err := h.Parse("./test_hosts.txt")
if err != nil {
t.Error(err)
}
2024-04-10 15:19:25 +08:00
next := h.firstUid
for next != 0 {
node, _ := h.GetNode(next)
fmt.Printf("Last %d, Next: %d, IP: %s, Hosts: %s, Comment: %s\n", node.LastUID(), node.NextUID(), node.IP(), node.Hosts(), node.Comment())
next = node.NextUID()
2024-04-10 11:19:04 +08:00
}
2024-04-10 15:19:25 +08:00
data := h.ListHostsByIP("11.22.33.44")
if len(data) != 2 {
t.Error("Expected 2, got ", len(data))
} else {
t.Log(data)
}
data = h.ListIPsByHost("dns.b612.me")
if len(data) < 1 || data[0] != "4.5.6.7" {
t.Error("Expected 4.5.6.7, got ", data)
} else {
t.Log(data)
}
err = h.RemoveHosts("dns.b612.me")
if err != nil {
t.Error(err)
}
data = h.ListIPsByHost("dns.b612.me")
if len(data) > 0 {
t.Error("Expected 0, got ", len(data))
} else {
t.Log(data)
}
err = h.RemoveHosts("test.dns.set.b612.me")
if err != nil {
t.Error(err)
}
data = h.ListIPsByHost("remove.b612.me")
if len(data) < 1 || data[0] != "11.22.33.44" {
t.Error("Expected 11.22.33.44, got ", data)
} else {
t.Log(data)
}
nodes := h.ListByIP("11.22.33.44")
if nodes == nil {
t.Error("Expected not nil, got ", nodes)
} else {
t.Log(nodes)
}
nodes[0].AddHosts("hello.b612.me")
err = h.UpdateNode(nodes[0])
2024-04-10 11:19:04 +08:00
if err != nil {
t.Error(err)
}
2024-04-10 15:19:25 +08:00
data = h.ListIPsByHost("hello.b612.me")
if len(data) < 1 || data[0] != "11.22.33.44" {
t.Error("Not Expected Data", data)
} else {
t.Log(data)
2024-04-10 11:19:04 +08:00
}
2024-04-10 15:19:25 +08:00
insertNode := new(HostNode)
insertNode.SetIP("11.11.11.11")
insertNode.SetHosts("insert.b612.me")
insertNode.SetComment("Insert Node")
insertNode.SetNextUID(nodes[0].UID())
insertNode.SetLastUID(nodes[0].LastUID())
err = h.InsertNode(insertNode)
2024-04-10 11:19:04 +08:00
if err != nil {
t.Error(err)
}
2024-04-10 15:19:25 +08:00
data = h.ListIPsByHost("insert.b612.me")
if len(data) < 1 || data[0] != "11.11.11.11" {
t.Error("Expected 11.11.11.11 got ", data)
} else {
t.Log(data)
}
err = h.SaveAs(filepath.Join(tmpDir, "test_hosts_01.txt"))
2024-04-10 15:19:25 +08:00
if err != nil {
t.Error(err)
}
err = h.DeleteNode(insertNode)
if err != nil {
t.Error(err)
}
data = h.ListIPsByHost("insert.b612.me")
if len(data) > 0 {
t.Error("Expected 0 got ", data)
} else {
t.Log(data)
}
for i := 0; i < 100; i++ {
err = h.RemoveHosts("release-ftpd")
if err != nil {
t.Error(err)
}
err = h.AddHosts("2.3.4.9", "release-ftpd")
if err != nil {
t.Error(err)
}
}
2024-04-10 15:52:57 +08:00
err = h.SetHostIPs("ssh.b612.me", "9.9.9.9")
if err != nil {
t.Error(err)
}
data = h.ListIPsByHost("ssh.b612.me")
if len(data) == 0 {
t.Error("Expected 1 got ", data)
} else {
t.Log(data)
}
err = h.SetIPHosts("10.10.10.10", "ssh.b612.me", "ssr.b612.me")
if len(data) == 0 {
t.Error("Expected 1 got ", data)
}
err = h.SaveAs(filepath.Join(tmpDir, "test_hosts_02.txt"))
2024-04-10 15:19:25 +08:00
if err != nil {
t.Error(err)
}
}
func BenchmarkAddHosts(b *testing.B) {
var h = NewHosts()
err := h.Parse("./test_hosts.txt")
if err != nil {
b.Error(err)
}
for i := 0; i < b.N; i++ {
err = h.AddHosts("1.3.4.5", "test.b612.me")
if err != nil {
b.Error(err)
}
2024-04-10 11:19:04 +08:00
}
2020-06-08 14:52:16 +08:00
}
func TestParseHandlesEmptyAndNoTrailingNewline(t *testing.T) {
t.Run("empty file", func(t *testing.T) {
h := NewHosts()
path := filepath.Join(t.TempDir(), "hosts.empty")
if err := os.WriteFile(path, nil, 0o644); err != nil {
t.Fatal(err)
}
if err := h.Parse(path); err != nil {
t.Fatal(err)
}
if got := h.List(); len(got) != 0 {
t.Fatalf("expected empty hosts list, got %d entries", len(got))
}
})
t.Run("last line without newline", func(t *testing.T) {
h := NewHosts()
path := filepath.Join(t.TempDir(), "hosts.nonewline")
if err := os.WriteFile(path, []byte("1.2.3.4 example.test"), 0o644); err != nil {
t.Fatal(err)
}
if err := h.Parse(path); err != nil {
t.Fatal(err)
}
if got := h.ListIPsByHost("example.test"); len(got) != 1 || got[0] != "1.2.3.4" {
t.Fatalf("expected last line to be parsed, got %v", got)
}
node, err := h.GetLatestNode()
if err != nil {
t.Fatal(err)
}
if node.NextUID() != 0 {
t.Fatalf("expected last node next uid 0, got %d", node.NextUID())
}
})
}
func TestAddHostsAndAddNodeWorkOnEmptyModel(t *testing.T) {
t.Run("add hosts", func(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "example.test"); err != nil {
t.Fatal(err)
}
if got := h.ListFirstIPByHost("example.test"); got != "1.2.3.4" {
t.Fatalf("expected inserted host ip, got %q", got)
}
out, err := h.Build()
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(out), "1.2.3.4 example.test") {
t.Fatalf("unexpected build output: %q", out)
}
})
t.Run("add node", func(t *testing.T) {
h := NewHosts()
node := &HostNode{}
node.SetIP("5.6.7.8")
node.SetHosts("node.test")
if err := h.AddNode(node); err != nil {
t.Fatal(err)
}
if node.UID() == 0 {
t.Fatal("expected node uid to be assigned")
}
if got := h.ListFirstIPByHost("node.test"); got != "5.6.7.8" {
t.Fatalf("expected inserted node ip, got %q", got)
}
})
}
func TestInsertNodeByDataInsertsAndLinksNode(t *testing.T) {
h := NewHosts()
path := filepath.Join(t.TempDir(), "hosts.insert")
if err := os.WriteFile(path, []byte("2.2.2.2 anchor.test\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := h.Parse(path); err != nil {
t.Fatal(err)
}
anchor, err := h.GetFirstNode()
if err != nil {
t.Fatal(err)
}
if err := h.InsertNodeByData(anchor, true, "before", "1.1.1.1", "before.test"); err != nil {
t.Fatal(err)
}
if err := h.InsertNodeByData(anchor, false, "after", "3.3.3.3", "after.test"); err != nil {
t.Fatal(err)
}
nodes := h.List()
if len(nodes) != 3 {
t.Fatalf("expected 3 nodes after insert, got %d", len(nodes))
}
if nodes[0].IP() != "1.1.1.1" || nodes[1].IP() != "2.2.2.2" || nodes[2].IP() != "3.3.3.3" {
t.Fatalf("unexpected node order: %q, %q, %q", nodes[0].IP(), nodes[1].IP(), nodes[2].IP())
}
if got := h.ListFirstIPByHost("before.test"); got != "1.1.1.1" {
t.Fatalf("expected before node to be indexed, got %q", got)
}
if got := h.ListFirstIPByHost("after.test"); got != "3.3.3.3" {
t.Fatalf("expected after node to be indexed, got %q", got)
}
if nodes[0].NextUID() != nodes[1].UID() || nodes[1].LastUID() != nodes[0].UID() {
t.Fatalf("before/anchor linkage broken: before.next=%d anchor.uid=%d anchor.last=%d", nodes[0].NextUID(), nodes[1].UID(), nodes[1].LastUID())
}
if nodes[1].NextUID() != nodes[2].UID() || nodes[2].LastUID() != nodes[1].UID() {
t.Fatalf("anchor/after linkage broken: anchor.next=%d after.uid=%d after.last=%d", nodes[1].NextUID(), nodes[2].UID(), nodes[2].LastUID())
}
}
func TestInsertNodeByDataRejectsNilAnchor(t *testing.T) {
h := NewHosts()
path := filepath.Join(t.TempDir(), "hosts.insert.nil")
if err := os.WriteFile(path, []byte("2.2.2.2 anchor.test\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := h.Parse(path); err != nil {
t.Fatal(err)
}
if err := h.InsertNodeByData(nil, true, "before", "1.1.1.1", "before.test"); err == nil {
t.Fatal("expected nil anchor error")
}
}
func TestSetIPHostsUpdatesReverseIndex(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "old.test"); err != nil {
t.Fatal(err)
}
if err := h.SetIPHosts("1.2.3.4", "new.test"); err != nil {
t.Fatal(err)
}
if got := h.ListIPsByHost("new.test"); len(got) != 1 || got[0] != "1.2.3.4" {
t.Fatalf("expected new reverse index, got %v", got)
}
if got := h.ListIPsByHost("old.test"); len(got) != 0 {
t.Fatalf("expected old reverse index to be removed, got %v", got)
}
}
func TestSetIPHostsDeduplicatesHosts(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "old.test"); err != nil {
t.Fatal(err)
}
if err := h.SetIPHosts("1.2.3.4", "new.test", "new.test"); err != nil {
t.Fatal(err)
}
if got := h.ListHostsByIP("1.2.3.4"); len(got) != 1 || got[0] != "new.test" {
t.Fatalf("expected deduplicated ip mapping, got %v", got)
}
if got := h.ListIPsByHost("new.test"); len(got) != 1 || got[0] != "1.2.3.4" {
t.Fatalf("expected deduplicated reverse index, got %v", got)
}
}
func TestSetIPHostsReplacesMultipleSameIPNodes(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "first.test"); err != nil {
t.Fatal(err)
}
if err := h.AddHosts("1.2.3.4", "second.test"); err != nil {
t.Fatal(err)
}
if err := h.AddHosts("5.6.7.8", "tail.test"); err != nil {
t.Fatal(err)
}
if err := h.SetIPHosts("1.2.3.4", "new.test"); err != nil {
t.Fatal(err)
}
if got := h.ListHostsByIP("1.2.3.4"); len(got) != 1 || got[0] != "new.test" {
t.Fatalf("expected replaced same-ip mappings, got %v", got)
}
if got := h.ListIPsByHost("first.test"); len(got) != 0 {
t.Fatalf("expected first old host to disappear, got %v", got)
}
if got := h.ListIPsByHost("second.test"); len(got) != 0 {
t.Fatalf("expected second old host to disappear, got %v", got)
}
if got := h.ListFirstIPByHost("tail.test"); got != "5.6.7.8" {
t.Fatalf("expected tail node to remain linked, got %q", got)
}
nodes := h.List()
if len(nodes) != 2 || nodes[0].IP() != "5.6.7.8" || nodes[1].IP() != "1.2.3.4" {
t.Fatalf("unexpected node list after SetIPHosts: %#v", nodes)
}
if nodes[0].NextUID() != nodes[1].UID() || nodes[1].LastUID() != nodes[0].UID() {
t.Fatalf("remaining node linkage broken: first.next=%d second.uid=%d second.last=%d", nodes[0].NextUID(), nodes[1].UID(), nodes[1].LastUID())
}
}
func TestSetHostIPsReplacesMappingsInOneOperation(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "old.test"); err != nil {
t.Fatal(err)
}
if err := h.SetHostIPs("old.test", "2.2.2.2", "3.3.3.3"); err != nil {
t.Fatal(err)
}
if got := h.ListIPsByHost("old.test"); len(got) != 2 || got[0] != "2.2.2.2" || got[1] != "3.3.3.3" {
t.Fatalf("expected replaced host ip mappings, got %v", got)
}
if got := h.ListHostsByIP("1.2.3.4"); len(got) != 0 {
t.Fatalf("expected old ip mapping to be removed, got %v", got)
}
}
func TestSetIPHostsRejectsInvalidInputWithoutMutating(t *testing.T) {
tests := []struct {
name string
ip string
hosts []string
}{
{name: "bad ip", ip: "bad-ip", hosts: []string{"new.test"}},
{name: "empty host", ip: "1.2.3.4", hosts: []string{""}},
{name: "comment host", ip: "1.2.3.4", hosts: []string{"#bad.test"}},
{name: "missing host", ip: "1.2.3.4"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "old.test"); err != nil {
t.Fatal(err)
}
if err := h.SetIPHosts(tt.ip, tt.hosts...); err == nil {
t.Fatal("expected invalid SetIPHosts input to fail")
}
if got := h.ListIPsByHost("old.test"); len(got) != 1 || got[0] != "1.2.3.4" {
t.Fatalf("old host mapping should remain after failed SetIPHosts, got %v", got)
}
if got := h.ListHostsByIP("1.2.3.4"); len(got) != 1 || got[0] != "old.test" {
t.Fatalf("ip index should remain after failed SetIPHosts, got %v", got)
}
if got := h.ListIPsByHost("new.test"); len(got) != 0 {
t.Fatalf("failed SetIPHosts should not add new host, got %v", got)
}
})
}
}
func TestSetHostIPsRejectsInvalidInputWithoutMutating(t *testing.T) {
tests := []struct {
name string
host string
ips []string
}{
{name: "empty host", host: "", ips: []string{"2.2.2.2"}},
{name: "comment host", host: "#old.test", ips: []string{"2.2.2.2"}},
{name: "bad ip", host: "old.test", ips: []string{"2.2.2.2", "bad-ip"}},
{name: "missing ip", host: "old.test"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "old.test"); err != nil {
t.Fatal(err)
}
if err := h.SetHostIPs(tt.host, tt.ips...); err == nil {
t.Fatal("expected invalid SetHostIPs input to fail")
}
if got := h.ListIPsByHost("old.test"); len(got) != 1 || got[0] != "1.2.3.4" {
t.Fatalf("old host mapping should remain after failed SetHostIPs, got %v", got)
}
if got := h.ListHostsByIP("1.2.3.4"); len(got) != 1 || got[0] != "old.test" {
t.Fatalf("ip index should remain after failed SetHostIPs, got %v", got)
}
if got := h.ListHostsByIP("2.2.2.2"); len(got) != 0 {
t.Fatalf("failed SetHostIPs should not add partial ip mapping, got %v", got)
}
})
}
}
func TestRemoveIPHostsKeepsSameIPOtherNodes(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "first.test"); err != nil {
t.Fatal(err)
}
if err := h.AddHosts("1.2.3.4", "second.test"); err != nil {
t.Fatal(err)
}
if err := h.RemoveIPHosts("1.2.3.4", "first.test"); err != nil {
t.Fatal(err)
}
if got := h.ListIPsByHost("first.test"); len(got) != 0 {
t.Fatalf("expected removed host to disappear, got %v", got)
}
if got := h.ListIPsByHost("second.test"); len(got) != 1 || got[0] != "1.2.3.4" {
t.Fatalf("expected same-ip sibling node to stay indexed, got %v", got)
}
if got := h.ListHostsByIP("1.2.3.4"); len(got) != 1 || got[0] != "second.test" {
t.Fatalf("expected ip index to keep sibling host, got %v", got)
}
}
func TestRemoveHostsKeepsSameIPOtherNodes(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "first.test"); err != nil {
t.Fatal(err)
}
if err := h.AddHosts("1.2.3.4", "second.test"); err != nil {
t.Fatal(err)
}
if err := h.RemoveHosts("first.test"); err != nil {
t.Fatal(err)
}
if got := h.ListHostsByIP("1.2.3.4"); len(got) != 1 || got[0] != "second.test" {
t.Fatalf("expected ip index to keep sibling host after RemoveHosts, got %v", got)
}
}
func TestRemoveIPsUnlinksAdjacentNodes(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "first.test"); err != nil {
t.Fatal(err)
}
if err := h.AddHosts("1.2.3.4", "second.test"); err != nil {
t.Fatal(err)
}
if err := h.AddHosts("5.6.7.8", "tail.test"); err != nil {
t.Fatal(err)
}
if err := h.RemoveIPs("1.2.3.4"); err != nil {
t.Fatal(err)
}
nodes := h.List()
if len(nodes) != 1 || nodes[0].IP() != "5.6.7.8" || nodes[0].LastUID() != 0 || nodes[0].NextUID() != 0 {
t.Fatalf("expected only tail node with clean links, got %#v", nodes)
}
if got := h.ListHostsByIP("1.2.3.4"); len(got) != 0 {
t.Fatalf("expected removed ip index to be empty, got %v", got)
}
if got := h.ListFirstIPByHost("tail.test"); got != "5.6.7.8" {
t.Fatalf("expected tail reverse index to remain, got %q", got)
}
}
func TestAddHostsRejectsInvalidInput(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("not-an-ip", "bad.test"); err == nil {
t.Fatal("expected invalid ip error")
}
if got := h.ListHostsByIP("not-an-ip"); len(got) != 0 {
t.Fatalf("invalid ip should not be indexed, got %v", got)
}
if err := h.AddHosts("1.2.3.4", ""); err == nil {
t.Fatal("expected empty host error")
}
if got := h.ListHostsByIP("1.2.3.4"); len(got) != 0 {
t.Fatalf("empty host should not be indexed, got %v", got)
}
}
func TestInsertNodeByDataRejectsInvalidHostDataWithoutMutating(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("2.2.2.2", "anchor.test"); err != nil {
t.Fatal(err)
}
anchor, err := h.GetFirstNode()
if err != nil {
t.Fatal(err)
}
tests := []struct {
name string
ip string
hosts []string
}{
{name: "bad ip", ip: "not-an-ip", hosts: []string{"bad.test"}},
{name: "empty host", ip: "1.1.1.1", hosts: []string{""}},
{name: "comment host", ip: "1.1.1.1", hosts: []string{"#bad.test"}},
{name: "missing host", ip: "1.1.1.1"},
}
for _, tt := range tests {
if err := h.InsertNodeByData(anchor, false, "", tt.ip, tt.hosts...); err == nil {
t.Fatalf("%s: expected error", tt.name)
}
}
nodes := h.List()
if len(nodes) != 1 || nodes[0].IP() != "2.2.2.2" {
t.Fatalf("invalid insert should not mutate node list: %#v", nodes)
}
if got := h.ListHostsByIP("1.1.1.1"); len(got) != 0 {
t.Fatalf("invalid insert should not mutate ip index: %v", got)
}
if err := h.InsertNodeByData(anchor, true, "comment-only", ""); err != nil {
t.Fatalf("comment-only insert should remain valid: %v", err)
}
nodes = h.List()
if len(nodes) != 2 || !nodes[0].OnlyComment() || nodes[1].IP() != "2.2.2.2" {
t.Fatalf("comment-only insert mismatch: %#v", nodes)
}
}
func TestInsertNodeByDataRejectsEmptyNodeWithoutMutating(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("2.2.2.2", "anchor.test"); err != nil {
t.Fatal(err)
}
anchor, err := h.GetFirstNode()
if err != nil {
t.Fatal(err)
}
if err := h.InsertNodeByData(anchor, true, "", ""); err == nil {
t.Fatal("expected empty insert to fail")
}
nodes := h.List()
if len(nodes) != 1 || nodes[0].IP() != "2.2.2.2" {
t.Fatalf("empty insert should not mutate node list: %#v", nodes)
}
out, err := h.Build()
if err != nil {
t.Fatal(err)
}
if got, want := string(out), "2.2.2.2 anchor.test"+lineBreaker; got != want {
t.Fatalf("empty insert should not change output: got %q want %q", got, want)
}
}
func TestEmptyHostsBuildAndSaveAs(t *testing.T) {
h := NewHosts()
path := filepath.Join(t.TempDir(), "hosts.empty")
if err := os.WriteFile(path, nil, 0o644); err != nil {
t.Fatal(err)
}
if err := h.Parse(path); err != nil {
t.Fatal(err)
}
out, err := h.Build()
if err != nil {
t.Fatal(err)
}
if len(out) != 0 {
t.Fatalf("expected empty build output, got %q", out)
}
outPath := filepath.Join(t.TempDir(), "hosts.out")
if err := h.SaveAs(outPath); err != nil {
t.Fatal(err)
}
saved, err := os.ReadFile(outPath)
if err != nil {
t.Fatal(err)
}
if len(saved) != 0 {
t.Fatalf("expected empty saved file, got %q", saved)
}
}
func TestParsePreservesBlankAndRawLines(t *testing.T) {
h := NewHosts()
path := filepath.Join(t.TempDir(), "hosts.raw")
input := []byte("127.0.0.1 localhost\n\nbadline\n# tail comment\n")
if err := os.WriteFile(path, input, 0o644); err != nil {
t.Fatal(err)
}
if err := h.Parse(path); err != nil {
t.Fatal(err)
}
out, err := h.Build()
if err != nil {
t.Fatal(err)
}
got := string(out)
if !strings.Contains(got, "127.0.0.1 localhost"+lineBreaker+lineBreaker+"badline"+lineBreaker) {
t.Fatalf("expected blank/raw lines to be preserved, got %q", got)
}
if !strings.Contains(got, "# tail comment"+lineBreaker) {
t.Fatalf("expected comment line to be preserved, got %q", got)
}
}
func TestHostAccessorsReturnDetachedCopies(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "example.test"); err != nil {
t.Fatal(err)
}
node, err := h.GetFirstNode()
if err != nil {
t.Fatal(err)
}
node.SetIP("9.9.9.9")
node.SetHosts("mutated.test")
if got := h.ListFirstIPByHost("example.test"); got != "1.2.3.4" {
t.Fatalf("detached copy mutated internal host index: %q", got)
}
if got := h.ListFirstIPByHost("mutated.test"); got != "" {
t.Fatalf("detached copy should not create new host index: %q", got)
}
out, err := h.Build()
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(out), "9.9.9.9 mutated.test") {
t.Fatalf("detached copy leaked into build output: %q", out)
}
}
func TestUpdateNodeRejectsInvalidMutationAndPreservesState(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "example.test"); err != nil {
t.Fatal(err)
}
node, err := h.GetFirstNode()
if err != nil {
t.Fatal(err)
}
node.SetIP("bad-ip")
if err := h.UpdateNode(node); err == nil {
t.Fatal("expected invalid update to fail")
}
if got := h.ListFirstIPByHost("example.test"); got != "1.2.3.4" {
t.Fatalf("failed update should preserve previous index, got %q", got)
}
out, err := h.Build()
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(out), "1.2.3.4 example.test") || strings.Contains(string(out), "bad-ip") {
t.Fatalf("failed update should preserve previous output, got %q", out)
}
}
func TestUpdateNodeCommentOnlyStateReindexesCleanly(t *testing.T) {
h := NewHosts()
if err := h.AddHosts("1.2.3.4", "example.test"); err != nil {
t.Fatal(err)
}
node, err := h.GetFirstNode()
if err != nil {
t.Fatal(err)
}
node.SetIP("")
node.SetHosts()
node.SetComment("note")
if err := h.UpdateNode(node); err != nil {
t.Fatalf("comment-only update failed: %v", err)
}
if got := h.ListByIP(""); len(got) != 0 {
t.Fatalf("comment-only node should not be indexed under empty ip: %#v", got)
}
updated, err := h.GetNode(node.UID())
if err != nil {
t.Fatal(err)
}
if !updated.OnlyComment() {
t.Fatalf("comment-only node should keep onlyComment state: %#v", updated)
}
node = updated
node.SetIP("2.2.2.2")
node.SetHosts("restored.test")
if err := h.UpdateNode(node); err != nil {
t.Fatalf("restoring host entry failed: %v", err)
}
updated, err = h.GetNode(node.UID())
if err != nil {
t.Fatal(err)
}
if updated.OnlyComment() {
t.Fatalf("host entry should clear onlyComment after restore: %#v", updated)
}
if got := h.ListFirstIPByHost("restored.test"); got != "2.2.2.2" {
t.Fatalf("restored host index mismatch: %q", got)
}
if got := h.ListByIP(""); len(got) != 0 {
t.Fatalf("restored host should still avoid empty ip index: %#v", got)
}
}