package cache import ( "fmt" "net/netip" "testing" "time" "codeberg.org/miekg/dns" "codeberg.org/miekg/dns/rdata" ) func TestSetGet(t *testing.T) { c, err := NewCache(100, "") if err != nil { t.Fatal(err) } defer c.Stop() msg := new(dns.Msg) msg.Answer = append(msg.Answer, &dns.A{ Hdr: dns.Header{Name: "example.com.", Class: dns.ClassINET, TTL: 300}, A: rdata.A{Addr: netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }) key := Key{Name: "example.com.", Qtype: dns.TypeA, Class: dns.ClassINET} c.Set(key, msg, 300*time.Second) packed, ok := c.Get(key) if !ok { t.Fatal("expected cache hit") } msg.Data = packed if err := msg.Unpack(); err != nil { t.Fatal(err) } if len(msg.Answer) != 1 { t.Fatalf("expected 1 answer, got %d", len(msg.Answer)) } a, _ := msg.Answer[0].(*dns.A) if a.A.Addr != netip.AddrFrom4([4]byte{1, 2, 3, 4}) { t.Errorf("IP = %s, want 1.2.3.4", a.A.Addr) } } func TestExpiry(t *testing.T) { c, _ := NewCache(10, "") defer c.Stop() msg := new(dns.Msg) key := Key{Name: "x.com.", Qtype: dns.TypeA, Class: dns.ClassINET} c.Set(key, msg, 30*time.Millisecond) time.Sleep(60 * time.Millisecond) _, ok := c.Get(key) if ok { t.Fatal("expected miss after expiry") } } func TestEviction(t *testing.T) { c, _ := NewCache(2, "") defer c.Stop() for i := 0; i < 5; i++ { name := fmt.Sprintf("d%d.com.", i) msg := new(dns.Msg) c.Set(Key{Name: name, Qtype: dns.TypeA, Class: dns.ClassINET}, msg, 60*time.Second) } if c.Len() > 2 { t.Errorf("expected ≤2 entries, got %d", c.Len()) } } func TestComputeTTL(t *testing.T) { msg := new(dns.Msg) msg.Answer = append(msg.Answer, &dns.A{ Hdr: dns.Header{Name: "x.", Class: dns.ClassINET, TTL: 120}, }) if d := computeTTL(msg); d != 120*time.Second { t.Errorf("TTL = %v, want 120s", d) } } func TestNegativeTTL(t *testing.T) { msg := new(dns.Msg) msg.Rcode = dns.RcodeNameError msg.Ns = append(msg.Ns, &dns.SOA{ Hdr: dns.Header{Name: "com.", Class: dns.ClassINET, TTL: 900}, SOA: rdata.SOA{Minttl: 300}, }) if d := computeTTL(msg); d != 300*time.Second { t.Errorf("negative TTL = %v, want 300s", d) } } func TestRace(t *testing.T) { c, _ := NewCache(1000, "") defer c.Stop() done := make(chan struct{}) go func() { for i := 0; i < 100; i++ { msg := new(dns.Msg) c.Set(Key{Name: fmt.Sprintf("d%d.com.", i), Qtype: dns.TypeA, Class: dns.ClassINET}, msg, time.Second) } close(done) }() for i := 0; i < 100; i++ { c.Get(Key{Name: fmt.Sprintf("d%d.com.", i), Qtype: dns.TypeA, Class: dns.ClassINET}) } <-done c.Stats() c.Len() } func TestSQLitePersistence(t *testing.T) { dir := t.TempDir() dbPath := dir + "/cache.db" c, err := NewCache(100, dbPath) if err != nil { t.Fatal(err) } msg := new(dns.Msg) msg.Answer = append(msg.Answer, &dns.A{ Hdr: dns.Header{Name: "x.com.", Class: dns.ClassINET, TTL: 300}, A: rdata.A{Addr: netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }) key := Key{Name: "x.com.", Qtype: dns.TypeA, Class: dns.ClassINET} c.Set(key, msg, 300*time.Second) c.Stop() c2, err := NewCache(100, dbPath) if err != nil { t.Fatal(err) } defer c2.Stop() packed, ok := c2.Get(key) if !ok { t.Fatal("expected cache hit from SQLite load") } msg.Data = packed if err := msg.Unpack(); err != nil { t.Fatal(err) } a, _ := msg.Answer[0].(*dns.A) if a.A.Addr != netip.AddrFrom4([4]byte{1, 2, 3, 4}) { t.Errorf("IP = %s, want 1.2.3.4", a.A.Addr) } }