summaryrefslogtreecommitdiff
path: root/internal/cache/cache_test.go
diff options
context:
space:
mode:
authorradhitya <alif@radhitya.org>2026-06-14 14:36:32 +0700
committerradhitya <alif@radhitya.org>2026-06-14 14:36:32 +0700
commit4e6a897a0b55ee533c05f89fa38dbe0704f2798d (patch)
tree12d9700e53775503ad7ba2beb72bedfc64bdd70d /internal/cache/cache_test.go
parent3e44adc94f32bfe500730fcbf1c02cedf65b0a30 (diff)
dns recursive resolver(iterative, root hints, delegfation, glue, fallback), adblocker, dns cache
Diffstat (limited to 'internal/cache/cache_test.go')
-rw-r--r--internal/cache/cache_test.go140
1 files changed, 140 insertions, 0 deletions
diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go
new file mode 100644
index 0000000..6556dcb
--- /dev/null
+++ b/internal/cache/cache_test.go
@@ -0,0 +1,140 @@
+package cache
+
+import (
+ "fmt"
+ "net"
+ "testing"
+ "time"
+
+ "github.com/miekg/dns"
+)
+
+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.RR_Header{Name: "example.com.", Rrtype: dns.TypeA, Ttl:300},
+ A: net.IPv4(1,2,3,4),
+ })
+
+ key := Key{Name: "example.com.", Qtype: dns.TypeA, Class: dns.ClassINET}
+ c.Set(key, msg, 300*time.Second)
+
+ got, ok := c.Get(key)
+ if !ok {
+ t.Fatal("expected cache hit")
+ }
+ if len(got.Answer) != 1 {
+ t.Fatalf("expected 1 answer, got %d", len(got.Answer))
+ }
+ a, _ := got.Answer[0].(*dns.A)
+ if !a.A.Equal(net.IPv4(1,2,3,4)) {
+ t.Errorf("IP = %s, want 1.2.3.4", a.A)
+ }
+}
+
+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.RR_Header{Name: "x.", Rrtype: dns.TypeA, 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.RR_Header{Name: "com.", Rrtype: dns.TypeSOA, Ttl: 900},
+ 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.RR_Header{Name: "x.com.", Rrtype: dns.TypeA, Ttl: 300},
+ A: net.IPv4(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()
+
+ got, ok := c2.Get(key)
+ if !ok {
+ t.Fatal("expected cache hit from SQLite load")
+ }
+ a, _ := got.Answer[0].(*dns.A)
+ if !a.A.Equal(net.IPv4(1, 2, 3, 4)) {
+ t.Errorf("IP = %s, want 1.2.3.4", a.A)
+ }
+}