From 8a9c6e8a02c6fd7805a6d6100f9e57b2e6bbca8b Mon Sep 17 00:00:00 2001
From: Eugene Burkov <e.burkov@adguard.com>
Date: Mon, 16 Nov 2020 19:45:31 +0300
Subject: [PATCH] Pull request: cover with tests

Merge in DNS/adguard-home from 2271-cover-with-tests to master

Updates #2271.

Squashed commit of the following:

commit db6440efe05171bc15367a2996521848ca348053
Merge: db7fa726b bf4c256c7
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Nov 16 19:23:09 2020 +0300

    Merge branch 'master' into 2271-cover-with-tests

commit db7fa726bb91b08ec7aaa6c0c818c88b5feb87cd
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Nov 16 18:26:51 2020 +0300

    all: clean dependencies sum

commit b8dc6078c4bcc0de1b7e9073832de122f6fe38a4
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Nov 16 16:46:00 2020 +0300

    testutil: improve code quality

commit 001b7194682b1f00aa54dc5a28236faed5a5b02d
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Nov 16 16:29:49 2020 +0300

    testutil: enhance functionality

commit f6ccd91a4df6c56778eab8ae50e88e3818b20dd3
Merge: 43fa2eefb 6358240e9
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Nov 16 15:57:37 2020 +0300

    Merge branch 'master' into 2271-cover-with-tests

commit 43fa2eefbc10ef361603cacc1ca12092b12a057a
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Nov 16 14:55:15 2020 +0300

    querylog: replace fake log with real in tests

commit b95bee7565a14a02c80c78131b3ced224663dd8a
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Mon Nov 16 12:38:59 2020 +0300

    dnsfilter: replace thoughtless declaration with idiomatic one

commit a210b1586092e7ae91a9e67c972fa2d2f6baded6
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Fri Nov 13 19:00:25 2020 +0300

    all: refresh golibs dependencies

commit 4ff97bd1ade6c80e274ff5716e44df4eba55bdd9
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Fri Nov 13 18:38:47 2020 +0300

    all: remove std log

commit 542dbda10fefce9f46d15489712b163d919b1291
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Fri Nov 13 13:46:39 2020 +0300

    querylog: improve test logic and readability

commit 796d402385925e8e62a1b4c7bf56e4ceec22418c
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Thu Nov 12 19:06:42 2020 +0300

    all: improve code quality

commit e81894c11ef15b0453e8e5297f1349936a32f9dd
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Thu Nov 12 18:32:30 2020 +0300

    all: cover with tests

commit 252d81fc8a50a91b02cf0f6f35cc22178a2a4d90
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Thu Nov 12 17:32:01 2020 +0300

    all: cover with tests
---
 HACKING.md                                  |   4 +
 go.mod                                      |   2 +-
 go.sum                                      |   8 +-
 internal/dnsfilter/dnsfilter_test.go        |  15 ++
 internal/dnsfilter/sb_pc_test.go            |  45 ++++++
 internal/dnsforward/dnsforward_http_test.go | 170 ++++++++++++++++++++
 internal/dnsforward/dnsforward_test.go      |   3 +
 internal/querylog/decode_test.go            |  43 +++++
 internal/querylog/qlog_test.go              |  19 ++-
 internal/testutil/testutil.go               |  28 ++++
 10 files changed, 324 insertions(+), 13 deletions(-)
 create mode 100644 internal/dnsforward/dnsforward_http_test.go

diff --git a/HACKING.md b/HACKING.md
index 36a67ab8..8df23efd 100644
--- a/HACKING.md
+++ b/HACKING.md
@@ -34,6 +34,10 @@ find out about how we **want** our code to look like.
  *  Document everything, including unexported top-level identifiers, to build
     a habit of writing documentation.
 
+ *  Don't put variable names into any kind of quotes.
+
+ *  Don't use naked `return`s.
+
  *  Don't use underscores in file and package names, unless they're build tags
     or for tests.  This is to prevent accidental build errors with weird tags.
 
diff --git a/go.mod b/go.mod
index 398c6961..467dfee6 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.14
 
 require (
 	github.com/AdguardTeam/dnsproxy v0.33.2
-	github.com/AdguardTeam/golibs v0.4.2
+	github.com/AdguardTeam/golibs v0.4.3
 	github.com/AdguardTeam/urlfilter v0.12.3
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/beefsack/go-rate v0.0.0-20200827232406-6cde80facd47 // indirect
diff --git a/go.sum b/go.sum
index bfafb2f9..65a1fd0c 100644
--- a/go.sum
+++ b/go.sum
@@ -7,13 +7,13 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
-github.com/AdguardTeam/dnsproxy v0.33.1 h1:rEAS1fBEQ3JslzsfkcyMRV96OeBWFnKzXvksduI0ous=
-github.com/AdguardTeam/dnsproxy v0.33.1/go.mod h1:kLi6lMpErnZThy5haiRSis4q0KTB8uPWO4JQsU1EDJA=
 github.com/AdguardTeam/dnsproxy v0.33.2 h1:k5aMcsw3TA/G2DR8EjIkwutDPuuRkKh8xij4cFWC6Fk=
 github.com/AdguardTeam/dnsproxy v0.33.2/go.mod h1:kLi6lMpErnZThy5haiRSis4q0KTB8uPWO4JQsU1EDJA=
 github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=
 github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
+github.com/AdguardTeam/golibs v0.4.3 h1:nXTLLLlIyU4BSRF0An5azS0uimSK/YpIMOBAO0/v1RY=
+github.com/AdguardTeam/golibs v0.4.3/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
 github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
 github.com/AdguardTeam/urlfilter v0.12.3 h1:FMjQG0eTgrr8xA3z2zaLVcCgGdpzoECPGWwgPjtwPNs=
 github.com/AdguardTeam/urlfilter v0.12.3/go.mod h1:1fcCQx5TGJANrQN6sHNNM9KPBl7qx7BJml45ko6vru0=
@@ -325,8 +325,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
 golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
 golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
-golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -370,8 +368,6 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf h1:kt3wY1Lu5MJAnKTfoMR52Cu4gwvna4VTzNOiT8tY73s=
-golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201109165425-215b40eba54c h1:+B+zPA6081G5cEb2triOIJpcvSW4AYzmIyWAqMn2JAc=
 golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/internal/dnsfilter/dnsfilter_test.go b/internal/dnsfilter/dnsfilter_test.go
index 357e24c4..bfe06caa 100644
--- a/internal/dnsfilter/dnsfilter_test.go
+++ b/internal/dnsfilter/dnsfilter_test.go
@@ -1,11 +1,14 @@
 package dnsfilter
 
 import (
+	"bytes"
 	"fmt"
 	"net"
+	"strings"
 	"testing"
 
 	"github.com/AdguardTeam/AdGuardHome/internal/testutil"
+	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/urlfilter/rules"
 	"github.com/miekg/dns"
 	"github.com/stretchr/testify/assert"
@@ -142,10 +145,17 @@ func TestEtcHostsMatching(t *testing.T) {
 // SAFE BROWSING
 
 func TestSafeBrowsing(t *testing.T) {
+	logOutput := &bytes.Buffer{}
+	testutil.ReplaceLogWriter(t, logOutput)
+	testutil.ReplaceLogLevel(t, log.DEBUG)
+
 	d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
 	defer d.Close()
 	gctx.stats.Safebrowsing.Requests = 0
 	d.checkMatch(t, "wmconvirus.narod.ru")
+
+	assert.True(t, strings.Contains(logOutput.String(), "SafeBrowsing lookup for wmconvirus.narod.ru"))
+
 	d.checkMatch(t, "test.wmconvirus.narod.ru")
 	d.checkMatchEmpty(t, "yandex.ru")
 	d.checkMatchEmpty(t, "pornhub.com")
@@ -328,9 +338,14 @@ func TestSafeSearchCacheGoogle(t *testing.T) {
 // PARENTAL
 
 func TestParentalControl(t *testing.T) {
+	logOutput := &bytes.Buffer{}
+	testutil.ReplaceLogWriter(t, logOutput)
+	testutil.ReplaceLogLevel(t, log.DEBUG)
+
 	d := NewForTest(&Config{ParentalEnabled: true}, nil)
 	defer d.Close()
 	d.checkMatch(t, "pornhub.com")
+	assert.True(t, strings.Contains(logOutput.String(), "Parental lookup for pornhub.com"))
 	d.checkMatch(t, "www.pornhub.com")
 	d.checkMatchEmpty(t, "www.yandex.ru")
 	d.checkMatchEmpty(t, "yandex.ru")
diff --git a/internal/dnsfilter/sb_pc_test.go b/internal/dnsfilter/sb_pc_test.go
index 93746363..71e59446 100644
--- a/internal/dnsfilter/sb_pc_test.go
+++ b/internal/dnsfilter/sb_pc_test.go
@@ -5,7 +5,9 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/agherr"
 	"github.com/AdguardTeam/golibs/cache"
+	"github.com/miekg/dns"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -88,4 +90,47 @@ func TestSafeBrowsingCache(t *testing.T) {
 	hash = sha256.Sum256([]byte("nonexisting.com"))
 	_, ok = c.hashToHost[hash]
 	assert.True(t, ok)
+
+	c = &sbCtx{
+		svc:       "SafeBrowsing",
+		cacheTime: 100,
+	}
+	conf = cache.Config{}
+	c.cache = cache.New(conf)
+
+	hash = sha256.Sum256([]byte("sub.host.com"))
+	c.hashToHost = make(map[[32]byte]string)
+	c.hashToHost[hash] = "sub.host.com"
+
+	c.cache.Set(hash[0:2], make([]byte, 32))
+	assert.Equal(t, 0, c.getCached())
+}
+
+// testErrUpstream implements upstream.Upstream interface for replacing real
+// upstream in tests.
+type testErrUpstream struct{}
+
+// Exchange always returns nil Msg and non-nil error.
+func (teu *testErrUpstream) Exchange(*dns.Msg) (*dns.Msg, error) {
+	return nil, agherr.Error("bad")
+}
+
+func (teu *testErrUpstream) Address() string {
+	return ""
+}
+
+func TestSBPC_checkErrorUpstream(t *testing.T) {
+	d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
+	defer d.Close()
+
+	ups := &testErrUpstream{}
+
+	d.safeBrowsingUpstream = ups
+	d.parentalUpstream = ups
+
+	_, err := d.checkSafeBrowsing("smthng.com")
+	assert.NotNil(t, err)
+
+	_, err = d.checkParental("smthng.com")
+	assert.NotNil(t, err)
 }
diff --git a/internal/dnsforward/dnsforward_http_test.go b/internal/dnsforward/dnsforward_http_test.go
new file mode 100644
index 00000000..19c4a0de
--- /dev/null
+++ b/internal/dnsforward/dnsforward_http_test.go
@@ -0,0 +1,170 @@
+package dnsforward
+
+import (
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
+	s := createTestServer(t)
+	err := s.Start()
+	assert.Nil(t, err)
+	defer assert.Nil(t, s.Stop())
+
+	defaultConf := s.conf
+
+	w := httptest.NewRecorder()
+
+	testCases := []struct {
+		name string
+		conf func() ServerConfig
+		want string
+	}{{
+		name: "all_right",
+		conf: func() ServerConfig {
+			return defaultConf
+		},
+		want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name: "fastest_addr",
+		conf: func() ServerConfig {
+			conf := defaultConf
+			conf.FastestAddr = true
+			return conf
+		},
+		want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name: "parallel",
+		conf: func() ServerConfig {
+			conf := defaultConf
+			conf.AllServers = true
+			return conf
+		},
+		want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			s.conf = tc.conf()
+			s.handleGetConfig(w, nil)
+			assert.Equal(t, tc.want, w.Body.String())
+
+			assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
+		})
+		w.Body.Reset()
+	}
+}
+
+func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
+	s := createTestServer(t)
+
+	defaultConf := s.conf
+
+	err := s.Start()
+	assert.Nil(t, err)
+	defer func() {
+		assert.Nil(t, s.Stop())
+	}()
+
+	w := httptest.NewRecorder()
+
+	const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}"
+	testCases := []struct {
+		name    string
+		req     string
+		wantSet string
+		wantGet string
+	}{{
+		name:    "upstream_dns",
+		req:     "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"]}",
+		wantSet: "",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "bootstraps",
+		req:     "{\"bootstrap_dns\":[\"9.9.9.10\"]}",
+		wantSet: "",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "blocking_mode_good",
+		req:     "{\"blocking_mode\":\"refused\"}",
+		wantSet: "",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "blocking_mode_bad",
+		req:     "{\"blocking_mode\":\"custom_ip\"}",
+		wantSet: "blocking_mode: incorrect value\n",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "ratelimit",
+		req:     "{\"ratelimit\":6}",
+		wantSet: "",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "edns_cs_enabled",
+		req:     "{\"edns_cs_enabled\":true}",
+		wantSet: "",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "dnssec_enabled",
+		req:     "{\"dnssec_enabled\":true}",
+		wantSet: "",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "cache_size",
+		req:     "{\"cache_size\":1024}",
+		wantSet: "",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "upstream_mode_parallel",
+		req:     "{\"upstream_mode\":\"parallel\"}",
+		wantSet: "",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "upstream_mode_fastest_addr",
+		req:     "{\"upstream_mode\":\"fastest_addr\"}",
+		wantSet: "",
+		wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
+	}, {
+		name:    "upstream_dns_bad",
+		req:     "{\"upstream_dns\":[\"\"]}",
+		wantSet: "wrong upstreams specification: missing port in address\n",
+		wantGet: defaultConfJSON,
+	}, {
+		name:    "bootstraps_bad",
+		req:     "{\"bootstrap_dns\":[\"a\"]}",
+		wantSet: "a can not be used as bootstrap dns cause: invalid bootstrap server address: Resolver a is not eligible to be a bootstrap DNS server\n",
+		wantGet: defaultConfJSON,
+	}, {
+		name:    "cache_bad_ttl",
+		req:     "{\"cache_ttl_min\":1024,\"cache_ttl_max\":512}",
+		wantSet: "cache_ttl_min must be less or equal than cache_ttl_max\n",
+		wantGet: defaultConfJSON,
+	}, {
+		name:    "upstream_mode_bad",
+		req:     "{\"upstream_mode\":\"somethingelse\"}",
+		wantSet: "upstream_mode: incorrect value\n",
+		wantGet: defaultConfJSON,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			rBody := ioutil.NopCloser(strings.NewReader(tc.req))
+			r, err := http.NewRequest(http.MethodPost, "http://example.com", rBody)
+			assert.Nil(t, err)
+
+			s.handleSetConfig(w, r)
+			assert.Equal(t, tc.wantSet, w.Body.String())
+			w.Body.Reset()
+
+			s.handleGetConfig(w, nil)
+			assert.Equal(t, tc.wantGet, w.Body.String())
+			w.Body.Reset()
+		})
+		s.conf = defaultConf
+	}
+}
diff --git a/internal/dnsforward/dnsforward_test.go b/internal/dnsforward/dnsforward_test.go
index ab2d1b72..327fbe82 100644
--- a/internal/dnsforward/dnsforward_test.go
+++ b/internal/dnsforward/dnsforward_test.go
@@ -756,11 +756,14 @@ func createTestServer(t *testing.T) *Server {
 	c.CacheTime = 30
 
 	f := dnsfilter.New(&c, filters)
+
 	s := NewServer(DNSCreateParams{DNSFilter: f})
 	s.conf.UDPListenAddr = &net.UDPAddr{Port: 0}
 	s.conf.TCPListenAddr = &net.TCPAddr{Port: 0}
 	s.conf.UpstreamDNS = []string{"8.8.8.8:53", "8.8.4.4:53"}
 	s.conf.FilteringConfig.ProtectionEnabled = true
+	s.conf.ConfigModified = func() {}
+
 	err := s.Prepare(nil)
 	assert.True(t, err == nil)
 	return s
diff --git a/internal/querylog/decode_test.go b/internal/querylog/decode_test.go
index 2b12313a..35863890 100644
--- a/internal/querylog/decode_test.go
+++ b/internal/querylog/decode_test.go
@@ -1,11 +1,54 @@
 package querylog
 
 import (
+	"bytes"
+	"strings"
 	"testing"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/testutil"
+	"github.com/AdguardTeam/golibs/log"
 	"github.com/stretchr/testify/assert"
 )
 
+func TestDecode_decodeQueryLog(t *testing.T) {
+	logOutput := &bytes.Buffer{}
+
+	testutil.ReplaceLogWriter(t, logOutput)
+	testutil.ReplaceLogLevel(t, log.DEBUG)
+
+	testCases := []struct {
+		name string
+		log  string
+		want string
+	}{{
+		name: "back_compatibility_all_right",
+		log:  `{"Question":"ULgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
+		want: "default",
+	}, {
+		name: "back_compatibility_bad_msg",
+		log:  `{"Question":"","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
+		want: "decodeLogEntry err: dns: overflow unpacking uint16\n",
+	}, {
+		name: "back_compatibility_bad_decoding",
+		log:  `{"Question":"LgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
+		want: "decodeLogEntry err: illegal base64 data at input byte 48\n",
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := logOutput.Write([]byte("default"))
+			assert.Nil(t, err)
+
+			l := &logEntry{}
+			decodeLogEntry(l, tc.log)
+
+			assert.True(t, strings.HasSuffix(logOutput.String(), tc.want), logOutput.String())
+
+			logOutput.Reset()
+		})
+	}
+}
+
 func TestJSON(t *testing.T) {
 	s := `
 	{"keystr":"val","obj":{"keybool":true,"keyint":123456}}
diff --git a/internal/querylog/qlog_test.go b/internal/querylog/qlog_test.go
index 35f33b99..4dec5175 100644
--- a/internal/querylog/qlog_test.go
+++ b/internal/querylog/qlog_test.go
@@ -231,13 +231,20 @@ func addEntry(l *queryLog, host, answerStr, client string) {
 	}
 	answer.A = net.ParseIP(answerStr)
 	a.Answer = append(a.Answer, answer)
-	res := dnsfilter.Result{}
+	res := dnsfilter.Result{
+		IsFiltered:  true,
+		Rule:        "SomeRule",
+		Reason:      dnsfilter.ReasonRewrite,
+		ServiceName: "SomeService",
+		FilterID:    1,
+	}
 	params := AddParams{
-		Question: &q,
-		Answer:   &a,
-		Result:   &res,
-		ClientIP: net.ParseIP(client),
-		Upstream: "upstream",
+		Question:   &q,
+		Answer:     &a,
+		OrigAnswer: &a,
+		Result:     &res,
+		ClientIP:   net.ParseIP(client),
+		Upstream:   "upstream",
 	}
 	l.Add(params)
 }
diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go
index f5106265..69187969 100644
--- a/internal/testutil/testutil.go
+++ b/internal/testutil/testutil.go
@@ -2,6 +2,7 @@
 package testutil
 
 import (
+	"io"
 	"io/ioutil"
 	"os"
 	"testing"
@@ -17,3 +18,30 @@ func DiscardLogOutput(m *testing.M) {
 
 	os.Exit(m.Run())
 }
+
+// ReplaceLogWriter moves logger output to w and uses Cleanup method of t to
+// revert changes.
+func ReplaceLogWriter(t *testing.T, w io.Writer) {
+	stdWriter := log.Writer()
+	t.Cleanup(func() {
+		log.SetOutput(stdWriter)
+	})
+	log.SetOutput(w)
+}
+
+// ReplaceLogLevel sets logging level to l and uses Cleanup method of t to
+// revert changes.
+func ReplaceLogLevel(t *testing.T, l int) {
+	switch l {
+	case log.INFO, log.DEBUG, log.ERROR:
+		// Go on.
+	default:
+		t.Fatalf("wrong l value (must be one of %v, %v, %v)", log.INFO, log.DEBUG, log.ERROR)
+	}
+
+	stdLevel := log.GetLevel()
+	t.Cleanup(func() {
+		log.SetLevel(stdLevel)
+	})
+	log.SetLevel(l)
+}