package cache import ( "context" "reflect" "testing" "time" "github.com/coredns/coredns/plugin/pkg/dnstest" "github.com/coredns/coredns/plugin/test" "github.com/miekg/dns" ) func TestNewItemPreservesMonotonicClock(t *testing.T) { now := time.Now() if reflect.DeepEqual(now, now.Round(0)) { t.Fatal("time.Now did not include a monotonic clock reading") } i := newItem(new(dns.Msg), now, time.Minute) // DeepEqual compares the complete time representation, including its // monotonic clock reading. Time.Equal intentionally ignores that detail. if !reflect.DeepEqual(i.stored, now) { t.Fatalf("stored time = %v; want original time %v", i.stored, now) } } // TestCacheDoesNotSynthesizeAA guards issue #6185. // // A cached answer that came from a non-authoritative upstream must not gain the // AA bit when it is served from the cache. Historically toMsg set AA // unconditionally, so the same query answered AA=0 on a miss and AA=1 on a hit. // Both directions are pinned here. func TestCacheDoesNotSynthesizeAA(t *testing.T) { c := New() c.Next = BackendHandler() // replies with Authoritative unset req := new(dns.Msg) req.SetQuestion("example.org.", dns.TypeA) // Miss: the answer is passed through, AA must stay 0. rec := dnstest.NewRecorder(&test.ResponseWriter{}) c.ServeDNS(context.TODO(), rec, req) if rec.Msg.Authoritative { t.Fatalf("cache miss: expected AA=0 from a non-authoritative backend, got AA=1") } // Hit: the same answer is rebuilt from the cached item, AA must still be 0. rec = dnstest.NewRecorder(&test.ResponseWriter{}) c.ServeDNS(context.TODO(), rec, req) if rec.Msg.Authoritative { t.Errorf("cache hit: expected AA=0, the cached answer was not authoritative, got AA=1") } } // TestCachePreservesAA is the AA=1 counterpart of // TestCacheDoesNotSynthesizeAA: an answer that *was* authoritative must still // be authoritative when it is replayed from cache. Without it the AA=0 test // alone would also pass if toMsg hardcoded AA to 0, silently breaking answers // from file, hosts and secondary. func TestCachePreservesAA(t *testing.T) { c := New() calls := 0 c.Next = authoritativeBackend(&calls) req := new(dns.Msg) req.SetQuestion("example.org.", dns.TypeA) // Miss: the authoritative answer is passed through. rec := dnstest.NewRecorder(&test.ResponseWriter{}) c.ServeDNS(context.TODO(), rec, req) if !rec.Msg.Authoritative { t.Fatalf("cache miss: expected AA=1 from an authoritative backend, got AA=0") } // Hit: rebuilt from the cached item, AA must survive the round trip. rec = dnstest.NewRecorder(&test.ResponseWriter{}) c.ServeDNS(context.TODO(), rec, req) if !rec.Msg.Authoritative { t.Errorf("cache hit: expected AA=1, the cached answer was authoritative, got AA=0") } // A second backend call would mean the "hit" was really a second miss, // which satisfies the AA=1 assertion without ever touching the cache. if calls != 1 { t.Errorf("expected exactly one backend call after miss and hit, got %d", calls) } }