commit:     26fd689d608e7cb99a173c645851cc22e66af2e3
Author:     Rahil Bhimjiani <me <AT> rahil <DOT> rocks>
AuthorDate: Mon Jan  1 05:53:54 2024 +0000
Commit:     Rahil Bhimjiani <rahil3108 <AT> gmail <DOT> com>
CommitDate: Mon Jan  1 05:53:54 2024 +0000
URL:        https://gitweb.gentoo.org/repo/proj/guru.git/commit/?id=26fd689d

net-dns/blocky: Fast and lightweight DNS proxy as ad-blocker

Supports DoH, DoT and prometheus+grafana integration with very versatile
cli and configuration options

Signed-off-by: Rahil Bhimjiani <me <AT> rahil.rocks>

 acct-group/blocky/blocky-0.ebuild                  |   10 +
 acct-group/blocky/metadata.xml                     |    8 +
 acct-user/blocky/blocky-0.ebuild                   |   13 +
 acct-user/blocky/metadata.xml                      |    8 +
 net-dns/blocky/Manifest                            |    3 +
 net-dns/blocky/blocky-0.22.ebuild                  |  104 +
 net-dns/blocky/blocky-9999.ebuild                  |  104 +
 net-dns/blocky/files/blocky-0.22.service           |   16 +
 .../blocky/files/disable-failed-tests-0.22.patch   | 2272 ++++++++++++++++++++
 net-dns/blocky/metadata.xml                        |   16 +
 10 files changed, 2554 insertions(+)

diff --git a/acct-group/blocky/blocky-0.ebuild 
b/acct-group/blocky/blocky-0.ebuild
new file mode 100644
index 0000000000..a85e41314e
--- /dev/null
+++ b/acct-group/blocky/blocky-0.ebuild
@@ -0,0 +1,10 @@
+# Copyright 2019-2023 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=8
+
+inherit acct-group
+
+DESCRIPTION="A group for net-dns/blocky"
+
+ACCT_GROUP_ID="69"

diff --git a/acct-group/blocky/metadata.xml b/acct-group/blocky/metadata.xml
new file mode 100644
index 0000000000..3e6b5d8ad4
--- /dev/null
+++ b/acct-group/blocky/metadata.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE pkgmetadata SYSTEM "https://www.gentoo.org/dtd/metadata.dtd";>
+<pkgmetadata>
+       <maintainer type="person">
+               <email>me@rahil.rocks</email>
+               <name>Rahil Bhimjiani</name>
+       </maintainer>
+</pkgmetadata>

diff --git a/acct-user/blocky/blocky-0.ebuild b/acct-user/blocky/blocky-0.ebuild
new file mode 100644
index 0000000000..f941ee07e2
--- /dev/null
+++ b/acct-user/blocky/blocky-0.ebuild
@@ -0,0 +1,13 @@
+# Copyright 2019-2023 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=8
+
+inherit acct-user
+
+DESCRIPTION="A user for net-dns/blocky"
+
+ACCT_USER_ID="100"
+ACCT_USER_GROUPS=( "blocky" )
+
+acct-user_add_deps

diff --git a/acct-user/blocky/metadata.xml b/acct-user/blocky/metadata.xml
new file mode 100644
index 0000000000..3e6b5d8ad4
--- /dev/null
+++ b/acct-user/blocky/metadata.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE pkgmetadata SYSTEM "https://www.gentoo.org/dtd/metadata.dtd";>
+<pkgmetadata>
+       <maintainer type="person">
+               <email>me@rahil.rocks</email>
+               <name>Rahil Bhimjiani</name>
+       </maintainer>
+</pkgmetadata>

diff --git a/net-dns/blocky/Manifest b/net-dns/blocky/Manifest
new file mode 100644
index 0000000000..cbfdf1469a
--- /dev/null
+++ b/net-dns/blocky/Manifest
@@ -0,0 +1,3 @@
+DIST blocky-0.22-deps.tar.xz 163869124 BLAKE2B 
e98614feaeb11604866d08003147871096c993cdb90c85bba46ea999a339570ca1500a80bddee202595372ec5d22baa6ec0345d8cf50ffee6d61dd8d6495d49f
 SHA512 
883e5dd66cf974c6d8c73a94c7677d5003ceb7a3ba68001c2d9d36e1e4d1ea64f0818d30240fe77f192ad3f95ede93127bed9adc9647da07a9b9bebc958ffd33
+DIST blocky-0.22-docs.tar.gz 5639822 BLAKE2B 
e177790deb7493f84d8217661e4e4296004a9db7e00ff8d37dbd3c4ed8b7ba0a7cf431067c103f25784e46ca7a8bf80532cdd8f305f3e4ce119890027f0eb186
 SHA512 
3eed0ff726479826fbefb4140c36bb26825124134d1fbbecf74a31b2fbdde993630adc645ea2e582ce8d415736cc85b20f64a835c87da98700b715c03d368e75
+DIST blocky-0.22.tar.gz 712644 BLAKE2B 
d74881026421337a0fc32a6af2cf6bff736e0130ac599cd80714f0cafff1e81864e0327bc773f8377106421642cf545b76407fca94f07425c428ff8512a7113b
 SHA512 
928ba882cb567f80b9b00c1ab74ba2fba0324b9e5fb6439789d50d8fd438a26f84772d36f91ef9c3a2351c798a399c15aa1b69927a2da11795edd576b7bae6a4

diff --git a/net-dns/blocky/blocky-0.22.ebuild 
b/net-dns/blocky/blocky-0.22.ebuild
new file mode 100644
index 0000000000..5b601c8825
--- /dev/null
+++ b/net-dns/blocky/blocky-0.22.ebuild
@@ -0,0 +1,104 @@
+# Copyright 1999-2024 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=8
+
+inherit fcaps go-module systemd shell-completion
+
+DESCRIPTION="Fast and lightweight DNS proxy as ad-blocker with many features 
written in Go"
+HOMEPAGE="https://github.com/0xERR0R/blocky/";
+
+DOCUMENTATION_COMMIT=9c6a86eb163e758686c5d6d4d5259deb086a8aa9
+
+if [[ ${PV} == 9999* ]]; then
+       inherit git-r3
+       EGIT_REPO_URI="https://github.com/0xERR0R/blocky.git";
+else
+       SRC_URI="
+       https://github.com/0xERR0R/blocky/archive/v${PV}.tar.gz -> ${P}.tar.gz
+       
https://github.com/rahilarious/gentoo-distfiles/releases/download/${P}/deps.tar.xz
 -> ${P}-deps.tar.xz
+       doc? ( 
https://github.com/0xERR0R/blocky/archive/${DOCUMENTATION_COMMIT}.tar.gz -> 
${P}-docs.tar.gz )
+"
+       KEYWORDS="~amd64"
+fi
+
+# main
+LICENSE="Apache-2.0"
+# deps
+LICENSE+=" AGPL-3 BSD-2 BSD ISC MIT MPL-2.0"
+SLOT="0"
+IUSE="doc"
+
+# RESTRICT="test"
+
+RDEPEND="
+       acct-user/blocky
+       acct-group/blocky
+"
+
+PATCHES=(
+       "${FILESDIR}"/disable-failed-tests-0.22.patch
+)
+
+FILECAPS=(
+       -m 755 'cap_net_bind_service=+ep' usr/bin/"${PN}"
+)
+
+src_unpack() {
+       if [[ ${PV} == 9999* ]]; then
+               git-r3_src_unpack
+               go-module_live_vendor
+               if use doc; then
+                       EGIT_BRANCH="gh-pages"
+                       EGIT_CHECKOUT_DIR="${WORKDIR}/${P}-doc"
+                       git-r3_src_unpack
+               fi
+       else
+               go-module_src_unpack
+       fi
+}
+
+src_compile() {
+       [[ ${PV} != 9999* ]] && export VERSION="${PV}"
+
+       # mimicking project's Dockerfile
+       emake GO_SKIP_GENERATE=yes GO_BUILD_FLAGS="-tags static -v " build
+
+       local shell
+       for shell in bash fish zsh; do
+               bin/"${PN}" completion "${shell}" > "${PN}"."${shell}" || die
+       done
+}
+
+src_test() {
+       # mimcking make test
+       ego run github.com/onsi/ginkgo/v2/ginkgo --label-filter="!e2e" 
--coverprofile=coverage.txt --covermode=atomic --cover -r -p
+       ego tool cover -html coverage.txt -o coverage.html
+}
+
+src_install() {
+       # primary program
+       dobin bin/"${PN}"
+
+       # secondary supplements
+       insinto /etc/"${PN}"
+       newins docs/config.yml config.yml.sample
+
+       newbashcomp "${PN}".bash "${PN}"
+       dofishcomp "${PN}".fish
+       newzshcomp "${PN}".zsh _"${PN}"
+
+       # TODO openrc services
+       systemd_newunit "${FILESDIR}"/blocky-0.22.service "${PN}".service
+
+       # docs
+       einstalldocs
+
+       if use doc; then
+               if [[ ${PV} == 9999* ]]; then
+                       dodoc -r ../"${P}"-doc/main/*
+               else
+                       dodoc -r ../"${PN}"-"${DOCUMENTATION_COMMIT}"/v"${PV}"/*
+               fi
+       fi
+}

diff --git a/net-dns/blocky/blocky-9999.ebuild 
b/net-dns/blocky/blocky-9999.ebuild
new file mode 100644
index 0000000000..3f5b49b9fc
--- /dev/null
+++ b/net-dns/blocky/blocky-9999.ebuild
@@ -0,0 +1,104 @@
+# Copyright 1999-2024 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+EAPI=8
+
+inherit fcaps go-module systemd shell-completion
+
+DESCRIPTION="Fast and lightweight DNS proxy as ad-blocker with many features 
written in Go"
+HOMEPAGE="https://github.com/0xERR0R/blocky/";
+
+DOCUMENTATION_COMMIT=9c6a86eb163e758686c5d6d4d5259deb086a8aa9
+
+if [[ ${PV} == 9999* ]]; then
+       inherit git-r3
+       EGIT_REPO_URI="https://github.com/0xERR0R/blocky.git";
+else
+       SRC_URI="
+       https://github.com/0xERR0R/blocky/archive/v${PV}.tar.gz -> ${P}.tar.gz
+       
https://github.com/rahilarious/gentoo-distfiles/releases/download/${P}/deps.tar.xz
 -> ${P}-deps.tar.xz
+       doc? ( 
https://github.com/0xERR0R/blocky/archive/${DOCUMENTATION_COMMIT}.tar.gz -> 
${P}-docs.tar.gz )
+"
+       KEYWORDS="~amd64"
+fi
+
+# main
+LICENSE="Apache-2.0"
+# deps
+LICENSE+=" AGPL-3 BSD-2 BSD ISC MIT MPL-2.0"
+SLOT="0"
+IUSE="doc"
+
+RESTRICT="test"
+
+RDEPEND="
+       acct-user/blocky
+       acct-group/blocky
+"
+
+# PATCHES=(
+#      "${FILESDIR}"/disable-failed-tests-0.22.patch
+# )
+
+FILECAPS=(
+       -m 755 'cap_net_bind_service=+ep' usr/bin/"${PN}"
+)
+
+src_unpack() {
+       if [[ ${PV} == 9999* ]]; then
+               git-r3_src_unpack
+               go-module_live_vendor
+               if use doc; then
+                       EGIT_BRANCH="gh-pages"
+                       EGIT_CHECKOUT_DIR="${WORKDIR}/${P}-doc"
+                       git-r3_src_unpack
+               fi
+       else
+               go-module_src_unpack
+       fi
+}
+
+src_compile() {
+       [[ ${PV} != 9999* ]] && export VERSION="${PV}"
+
+       # mimicking project's Dockerfile
+       emake GO_SKIP_GENERATE=yes GO_BUILD_FLAGS="-tags static -v " build
+
+       local shell
+       for shell in bash fish zsh; do
+               bin/"${PN}" completion "${shell}" > "${PN}"."${shell}" || die
+       done
+}
+
+src_test() {
+       # mimcking make test
+       ego run github.com/onsi/ginkgo/v2/ginkgo --label-filter="!e2e" 
--coverprofile=coverage.txt --covermode=atomic --cover -r -p
+       ego tool cover -html coverage.txt -o coverage.html
+}
+
+src_install() {
+       # primary program
+       dobin bin/"${PN}"
+
+       # secondary supplements
+       insinto /etc/"${PN}"
+       newins docs/config.yml config.yml.sample
+
+       newbashcomp "${PN}".bash "${PN}"
+       dofishcomp "${PN}".fish
+       newzshcomp "${PN}".zsh _"${PN}"
+
+       # TODO openrc services
+       systemd_newunit "${FILESDIR}"/blocky-0.22.service "${PN}".service
+
+       # docs
+       einstalldocs
+
+       if use doc; then
+               if [[ ${PV} == 9999* ]]; then
+                       dodoc -r ../"${P}"-doc/main/*
+               else
+                       dodoc -r ../"${PN}"-"${DOCUMENTATION_COMMIT}"/v"${PV}"/*
+               fi
+       fi
+}

diff --git a/net-dns/blocky/files/blocky-0.22.service 
b/net-dns/blocky/files/blocky-0.22.service
new file mode 100644
index 0000000000..ce40a31cee
--- /dev/null
+++ b/net-dns/blocky/files/blocky-0.22.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Fast and lightweight DNS proxy as ad-blocker
+Documentation=https://0xerr0r.github.io/blocky/
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+CapabilityBoundingSet=CAP_NET_BIND_SERVICE
+AmbientCapabilities=CAP_NET_BIND_SERVICE
+NoNewPrivileges=true
+User=blocky
+ExecStart=/usr/bin/blocky --config /etc/blocky/config.yml
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target

diff --git a/net-dns/blocky/files/disable-failed-tests-0.22.patch 
b/net-dns/blocky/files/disable-failed-tests-0.22.patch
new file mode 100644
index 0000000000..98883469df
--- /dev/null
+++ b/net-dns/blocky/files/disable-failed-tests-0.22.patch
@@ -0,0 +1,2272 @@
+diff --git a/cache/stringcache/chained_grouped_cache_test.go 
b/cache/stringcache/chained_grouped_cache_test.go
+deleted file mode 100644
+index e83f956..0000000
+--- a/cache/stringcache/chained_grouped_cache_test.go
++++ /dev/null
+@@ -1,93 +0,0 @@
+-package stringcache_test
+-
+-import (
+-      "github.com/0xERR0R/blocky/cache/stringcache"
+-      . "github.com/onsi/ginkgo/v2"
+-      . "github.com/onsi/gomega"
+-)
+-
+-var _ = Describe("Chained grouped cache", func() {
+-      Describe("Empty cache", func() {
+-              When("empty cache was created", func() {
+-                      cache := stringcache.NewChainedGroupedCache()
+-
+-                      It("should have element count of 0", func() {
+-                              
Expect(cache.ElementCount("someGroup")).Should(BeNumerically("==", 0))
+-                      })
+-
+-                      It("should not find any string", func() {
+-                              Expect(cache.Contains("searchString", 
[]string{"someGroup"})).Should(BeEmpty())
+-                      })
+-              })
+-      })
+-      Describe("Delegation", func() {
+-              When("Chained cache contains delegates", func() {
+-                      inMemoryCache1 := 
stringcache.NewInMemoryGroupedStringCache()
+-                      inMemoryCache2 := 
stringcache.NewInMemoryGroupedStringCache()
+-                      cache := 
stringcache.NewChainedGroupedCache(inMemoryCache1, inMemoryCache2)
+-
+-                      factory := cache.Refresh("group1")
+-
+-                      factory.AddEntry("string1")
+-                      factory.AddEntry("string2")
+-
+-                      It("cache should still have 0 element, since finish was 
not executed", func() {
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 0))
+-                      })
+-
+-                      It("factory has 4 elements (both caches)", func() {
+-                              
Expect(factory.Count()).Should(BeNumerically("==", 4))
+-                      })
+-
+-                      It("should have element count of 4", func() {
+-                              factory.Finish()
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 4))
+-                      })
+-
+-                      It("should find strings", func() {
+-                              Expect(cache.Contains("string1", 
[]string{"group1"})).Should(ConsistOf("group1"))
+-                              Expect(cache.Contains("string2", 
[]string{"group1", "someOtherGroup"})).Should(ConsistOf("group1"))
+-                      })
+-              })
+-      })
+-
+-      Describe("Cache refresh", func() {
+-              When("cache with 2 groups was created", func() {
+-                      inMemoryCache1 := 
stringcache.NewInMemoryGroupedStringCache()
+-                      inMemoryCache2 := 
stringcache.NewInMemoryGroupedStringCache()
+-                      cache := 
stringcache.NewChainedGroupedCache(inMemoryCache1, inMemoryCache2)
+-
+-                      factory := cache.Refresh("group1")
+-
+-                      factory.AddEntry("g1")
+-                      factory.AddEntry("both")
+-                      factory.Finish()
+-
+-                      factory = cache.Refresh("group2")
+-                      factory.AddEntry("g2")
+-                      factory.AddEntry("both")
+-                      factory.Finish()
+-
+-                      It("should contain 4 elements in 2 groups", func() {
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 4))
+-                              
Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 4))
+-                              Expect(cache.Contains("g1", []string{"group1", 
"group2"})).Should(ConsistOf("group1"))
+-                              Expect(cache.Contains("g2", []string{"group1", 
"group2"})).Should(ConsistOf("group2"))
+-                              Expect(cache.Contains("both", 
[]string{"group1", "group2"})).Should(ConsistOf("group1", "group2"))
+-                      })
+-
+-                      It("Should replace group content on refresh", func() {
+-                              factory := cache.Refresh("group1")
+-                              factory.AddEntry("newString")
+-                              factory.Finish()
+-
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 2))
+-                              
Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 4))
+-                              Expect(cache.Contains("g1", []string{"group1", 
"group2"})).Should(BeEmpty())
+-                              Expect(cache.Contains("newString", 
[]string{"group1", "group2"})).Should(ConsistOf("group1"))
+-                              Expect(cache.Contains("g2", []string{"group1", 
"group2"})).Should(ConsistOf("group2"))
+-                              Expect(cache.Contains("both", 
[]string{"group1", "group2"})).Should(ConsistOf("group2"))
+-                      })
+-              })
+-      })
+-})
+diff --git a/cache/stringcache/in_memory_grouped_cache_test.go 
b/cache/stringcache/in_memory_grouped_cache_test.go
+deleted file mode 100644
+index 7692935..0000000
+--- a/cache/stringcache/in_memory_grouped_cache_test.go
++++ /dev/null
+@@ -1,130 +0,0 @@
+-package stringcache_test
+-
+-import (
+-      "github.com/0xERR0R/blocky/cache/stringcache"
+-      . "github.com/onsi/ginkgo/v2"
+-      . "github.com/onsi/gomega"
+-)
+-
+-var _ = Describe("In-Memory grouped cache", func() {
+-      Describe("Empty cache", func() {
+-              When("empty cache was created", func() {
+-                      cache := stringcache.NewInMemoryGroupedStringCache()
+-
+-                      It("should have element count of 0", func() {
+-                              
Expect(cache.ElementCount("someGroup")).Should(BeNumerically("==", 0))
+-                      })
+-
+-                      It("should not find any string", func() {
+-                              Expect(cache.Contains("searchString", 
[]string{"someGroup"})).Should(BeEmpty())
+-                      })
+-              })
+-              When("cache with one empty group", func() {
+-                      cache := stringcache.NewInMemoryGroupedStringCache()
+-                      factory := cache.Refresh("group1")
+-                      factory.Finish()
+-
+-                      It("should have element count of 0", func() {
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 0))
+-                      })
+-
+-                      It("should not find any string", func() {
+-                              Expect(cache.Contains("searchString", 
[]string{"group1"})).Should(BeEmpty())
+-                      })
+-              })
+-      })
+-      Describe("Cache creation", func() {
+-              When("cache with 1 group was created", func() {
+-                      cache := stringcache.NewInMemoryGroupedStringCache()
+-
+-                      factory := cache.Refresh("group1")
+-
+-                      factory.AddEntry("string1")
+-                      factory.AddEntry("string2")
+-
+-                      It("cache should still have 0 element, since finish was 
not executed", func() {
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 0))
+-                      })
+-
+-                      It("factory has 2 elements", func() {
+-                              
Expect(factory.Count()).Should(BeNumerically("==", 2))
+-                      })
+-
+-                      It("should have element count of 2", func() {
+-                              factory.Finish()
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 2))
+-                      })
+-
+-                      It("should find strings", func() {
+-                              Expect(cache.Contains("string1", 
[]string{"group1"})).Should(ConsistOf("group1"))
+-                              Expect(cache.Contains("string2", 
[]string{"group1", "someOtherGroup"})).Should(ConsistOf("group1"))
+-                      })
+-              })
+-              When("String grouped cache is used", func() {
+-                      cache := stringcache.NewInMemoryGroupedStringCache()
+-                      factory := cache.Refresh("group1")
+-
+-                      factory.AddEntry("string1")
+-                      factory.AddEntry("/string2/")
+-                      factory.Finish()
+-
+-                      It("should ignore regex", func() {
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 1))
+-                              Expect(cache.Contains("string1", 
[]string{"group1"})).Should(ConsistOf("group1"))
+-                      })
+-              })
+-              When("Regex grouped cache is used", func() {
+-                      cache := stringcache.NewInMemoryGroupedRegexCache()
+-                      factory := cache.Refresh("group1")
+-
+-                      factory.AddEntry("string1")
+-                      factory.AddEntry("/string2/")
+-                      factory.Finish()
+-
+-                      It("should ignore non-regex", func() {
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 1))
+-                              Expect(cache.Contains("string1", 
[]string{"group1"})).Should(BeEmpty())
+-                              Expect(cache.Contains("string2", 
[]string{"group1"})).Should(ConsistOf("group1"))
+-                              Expect(cache.Contains("shouldalsomatchstring2", 
[]string{"group1"})).Should(ConsistOf("group1"))
+-                      })
+-              })
+-      })
+-
+-      Describe("Cache refresh", func() {
+-              When("cache with 2 groups was created", func() {
+-                      cache := stringcache.NewInMemoryGroupedStringCache()
+-
+-                      factory := cache.Refresh("group1")
+-
+-                      factory.AddEntry("g1")
+-                      factory.AddEntry("both")
+-                      factory.Finish()
+-
+-                      factory = cache.Refresh("group2")
+-                      factory.AddEntry("g2")
+-                      factory.AddEntry("both")
+-                      factory.Finish()
+-
+-                      It("should contain 4 elements in 2 groups", func() {
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 2))
+-                              
Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 2))
+-                              Expect(cache.Contains("g1", []string{"group1", 
"group2"})).Should(ConsistOf("group1"))
+-                              Expect(cache.Contains("g2", []string{"group1", 
"group2"})).Should(ConsistOf("group2"))
+-                              Expect(cache.Contains("both", 
[]string{"group1", "group2"})).Should(ConsistOf("group1", "group2"))
+-                      })
+-
+-                      It("Should replace group content on refresh", func() {
+-                              factory := cache.Refresh("group1")
+-                              factory.AddEntry("newString")
+-                              factory.Finish()
+-
+-                              
Expect(cache.ElementCount("group1")).Should(BeNumerically("==", 1))
+-                              
Expect(cache.ElementCount("group2")).Should(BeNumerically("==", 2))
+-                              Expect(cache.Contains("g1", []string{"group1", 
"group2"})).Should(BeEmpty())
+-                              Expect(cache.Contains("newString", 
[]string{"group1", "group2"})).Should(ConsistOf("group1"))
+-                              Expect(cache.Contains("g2", []string{"group1", 
"group2"})).Should(ConsistOf("group2"))
+-                              Expect(cache.Contains("both", 
[]string{"group1", "group2"})).Should(ConsistOf("group2"))
+-                      })
+-              })
+-      })
+-})
+diff --git a/lists/downloader_test.go b/lists/downloader_test.go
+deleted file mode 100644
+index 5387c86..0000000
+--- a/lists/downloader_test.go
++++ /dev/null
+@@ -1,218 +0,0 @@
+-package lists
+-
+-import (
+-      "errors"
+-      "io"
+-      "net"
+-      "net/http"
+-      "net/http/httptest"
+-      "strings"
+-      "sync/atomic"
+-      "time"
+-
+-      "github.com/0xERR0R/blocky/config"
+-      . "github.com/0xERR0R/blocky/evt"
+-      . "github.com/0xERR0R/blocky/helpertest"
+-      "github.com/0xERR0R/blocky/log"
+-      . "github.com/onsi/ginkgo/v2"
+-      . "github.com/onsi/gomega"
+-      "github.com/sirupsen/logrus/hooks/test"
+-)
+-
+-var _ = Describe("Downloader", func() {
+-      var (
+-              sutConfig                     config.DownloaderConfig
+-              sut                           *httpDownloader
+-              failedDownloadCountEvtChannel chan string
+-              loggerHook                    *test.Hook
+-      )
+-      BeforeEach(func() {
+-              var err error
+-
+-              sutConfig, err = config.WithDefaults[config.DownloaderConfig]()
+-              Expect(err).Should(Succeed())
+-
+-              failedDownloadCountEvtChannel = make(chan string, 5)
+-              // collect received events in the channel
+-              fn := func(url string) {
+-                      failedDownloadCountEvtChannel <- url
+-              }
+-              Expect(Bus().Subscribe(CachingFailedDownloadChanged, 
fn)).Should(Succeed())
+-              DeferCleanup(func() {
+-                      Expect(Bus().Unsubscribe(CachingFailedDownloadChanged, 
fn))
+-              })
+-
+-              loggerHook = test.NewGlobal()
+-              log.Log().AddHook(loggerHook)
+-              DeferCleanup(loggerHook.Reset)
+-      })
+-
+-      JustBeforeEach(func() {
+-              sut = newDownloader(sutConfig, nil)
+-      })
+-
+-      Describe("NewDownloader", func() {
+-              It("Should use provided parameters", func() {
+-                      transport := &http.Transport{}
+-
+-                      sut = NewDownloader(
+-                              config.DownloaderConfig{
+-                                      Attempts: 5,
+-                                      Cooldown: config.Duration(2 * 
time.Second),
+-                                      Timeout:  config.Duration(5 * 
time.Second),
+-                              },
+-                              transport,
+-                      ).(*httpDownloader)
+-
+-                      Expect(sut.cfg.Attempts).Should(BeNumerically("==", 5))
+-                      Expect(sut.cfg.Timeout).Should(BeNumerically("==", 
5*time.Second))
+-                      Expect(sut.cfg.Cooldown).Should(BeNumerically("==", 
2*time.Second))
+-                      
Expect(sut.client.Transport).Should(BeIdenticalTo(transport))
+-              })
+-      })
+-
+-      Describe("Download of a file", func() {
+-              var server *httptest.Server
+-              When("Download was successful", func() {
+-                      BeforeEach(func() {
+-                              server = TestServer("line.one\nline.two")
+-                              DeferCleanup(server.Close)
+-
+-                              sut = newDownloader(sutConfig, nil)
+-                      })
+-                      It("Should return all lines from the file", func() {
+-                              reader, err := sut.DownloadFile(server.URL)
+-
+-                              Expect(err).Should(Succeed())
+-                              Expect(reader).Should(Not(BeNil()))
+-                              DeferCleanup(reader.Close)
+-                              buf := new(strings.Builder)
+-                              _, err = io.Copy(buf, reader)
+-                              Expect(err).Should(Succeed())
+-                              
Expect(buf.String()).Should(Equal("line.one\nline.two"))
+-                      })
+-              })
+-              When("Server returns NOT_FOUND (404)", func() {
+-                      BeforeEach(func() {
+-                              server = 
httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req 
*http.Request) {
+-                                      rw.WriteHeader(http.StatusNotFound)
+-                              }))
+-                              DeferCleanup(server.Close)
+-
+-                              sutConfig.Attempts = 3
+-                      })
+-                      It("Should return error", func() {
+-                              reader, err := sut.DownloadFile(server.URL)
+-
+-                              Expect(err).Should(HaveOccurred())
+-                              Expect(reader).Should(BeNil())
+-                              Expect(err.Error()).Should(Equal("got status 
code 404"))
+-                              
Expect(failedDownloadCountEvtChannel).Should(HaveLen(3))
+-                              
Expect(failedDownloadCountEvtChannel).Should(Receive(Equal(server.URL)))
+-                      })
+-              })
+-              When("Wrong URL is defined", func() {
+-                      BeforeEach(func() {
+-                              sutConfig.Attempts = 1
+-                      })
+-                      It("Should return error", func() {
+-                              _, err := sut.DownloadFile("somewrongurl")
+-
+-                              Expect(err).Should(HaveOccurred())
+-                              
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("Can't download 
file: "))
+-                              // failed download event was emitted only once
+-                              
Expect(failedDownloadCountEvtChannel).Should(HaveLen(1))
+-                              
Expect(failedDownloadCountEvtChannel).Should(Receive(Equal("somewrongurl")))
+-                      })
+-              })
+-
+-              When("If timeout occurs on first request", func() {
+-                      var attempt uint64 = 1
+-
+-                      BeforeEach(func() {
+-                              sutConfig = config.DownloaderConfig{
+-                                      Timeout:  config.Duration(20 * 
time.Millisecond),
+-                                      Attempts: 3,
+-                                      Cooldown: 
config.Duration(time.Millisecond),
+-                              }
+-
+-                              // should produce a timeout on first attempt
+-                              server = 
httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req 
*http.Request) {
+-                                      a := atomic.LoadUint64(&attempt)
+-                                      atomic.AddUint64(&attempt, 1)
+-                                      if a == 1 {
+-                                              time.Sleep(500 * 
time.Millisecond)
+-                                      } else {
+-                                              _, err := 
rw.Write([]byte("blocked1.com"))
+-                                              Expect(err).Should(Succeed())
+-                                      }
+-                              }))
+-                              DeferCleanup(server.Close)
+-                      })
+-                      It("Should perform a retry and return file content", 
func() {
+-                              reader, err := sut.DownloadFile(server.URL)
+-                              Expect(err).Should(Succeed())
+-                              Expect(reader).Should(Not(BeNil()))
+-                              DeferCleanup(reader.Close)
+-
+-                              buf := new(strings.Builder)
+-                              _, err = io.Copy(buf, reader)
+-                              Expect(err).Should(Succeed())
+-                              
Expect(buf.String()).Should(Equal("blocked1.com"))
+-
+-                              // failed download event was emitted only once
+-                              
Expect(failedDownloadCountEvtChannel).Should(HaveLen(1))
+-                              
Expect(failedDownloadCountEvtChannel).Should(Receive(Equal(server.URL)))
+-                              
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("Temporary 
network err / Timeout occurred: "))
+-                      })
+-              })
+-              When("If timeout occurs on all request", func() {
+-                      BeforeEach(func() {
+-                              sutConfig = config.DownloaderConfig{
+-                                      Timeout:  config.Duration(10 * 
time.Millisecond),
+-                                      Attempts: 3,
+-                                      Cooldown: 
config.Duration(time.Millisecond),
+-                              }
+-
+-                              // should always produce a timeout
+-                              server = 
httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req 
*http.Request) {
+-                                      time.Sleep(20 * time.Millisecond)
+-                              }))
+-                              DeferCleanup(server.Close)
+-                      })
+-                      It("Should perform a retry until max retry attempt 
count is reached and return TransientError", func() {
+-                              reader, err := sut.DownloadFile(server.URL)
+-                              Expect(err).Should(HaveOccurred())
+-                              Expect(errors.As(err, 
new(*TransientError))).Should(BeTrue())
+-                              
Expect(err.Error()).Should(ContainSubstring("Timeout"))
+-                              Expect(reader).Should(BeNil())
+-
+-                              // failed download event was emitted 3 times
+-                              
Expect(failedDownloadCountEvtChannel).Should(HaveLen(3))
+-                              
Expect(failedDownloadCountEvtChannel).Should(Receive(Equal(server.URL)))
+-                      })
+-              })
+-              When("DNS resolution of passed URL fails", func() {
+-                      BeforeEach(func() {
+-                              sutConfig = config.DownloaderConfig{
+-                                      Timeout:  config.Duration(500 * 
time.Millisecond),
+-                                      Attempts: 3,
+-                                      Cooldown: 200 * 
config.Duration(time.Millisecond),
+-                              }
+-                      })
+-                      It("Should perform a retry until max retry attempt 
count is reached and return DNSError", func() {
+-                              reader, err := 
sut.DownloadFile("http://some.domain.which.does.not.exist";)
+-                              Expect(err).Should(HaveOccurred())
+-
+-                              var dnsError *net.DNSError
+-                              Expect(errors.As(err, 
&dnsError)).Should(BeTrue(), "received error %w", err)
+-                              Expect(reader).Should(BeNil())
+-
+-                              // failed download event was emitted 3 times
+-                              
Expect(failedDownloadCountEvtChannel).Should(HaveLen(3))
+-                              
Expect(failedDownloadCountEvtChannel).Should(Receive(Equal("http://some.domain.which.does.not.exist";)))
+-                              
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("Name resolution 
err: "))
+-                      })
+-              })
+-      })
+-})
+diff --git a/lists/list_cache_benchmark_test.go 
b/lists/list_cache_benchmark_test.go
+deleted file mode 100644
+index fedf2fd..0000000
+--- a/lists/list_cache_benchmark_test.go
++++ /dev/null
+@@ -1,29 +0,0 @@
+-package lists
+-
+-import (
+-      "testing"
+-
+-      "github.com/0xERR0R/blocky/config"
+-)
+-
+-func BenchmarkRefresh(b *testing.B) {
+-      file1, _ := createTestListFile(b.TempDir(), 100000)
+-      file2, _ := createTestListFile(b.TempDir(), 150000)
+-      file3, _ := createTestListFile(b.TempDir(), 130000)
+-      lists := map[string][]config.BytesSource{
+-              "gr1": config.NewBytesSources(file1, file2, file3),
+-      }
+-
+-      cfg := config.SourceLoadingConfig{
+-              Concurrency:   5,
+-              RefreshPeriod: config.Duration(-1),
+-      }
+-      downloader := NewDownloader(config.DownloaderConfig{}, nil)
+-      cache, _ := NewListCache(ListCacheTypeBlacklist, cfg, lists, downloader)
+-
+-      b.ReportAllocs()
+-
+-      for n := 0; n < b.N; n++ {
+-              cache.Refresh()
+-      }
+-}
+diff --git a/lists/list_cache_test.go b/lists/list_cache_test.go
+deleted file mode 100644
+index 2d08cfb..0000000
+--- a/lists/list_cache_test.go
++++ /dev/null
+@@ -1,496 +0,0 @@
+-package lists
+-
+-import (
+-      "bufio"
+-      "context"
+-      "errors"
+-      "fmt"
+-      "io"
+-      "math/rand"
+-      "net/http/httptest"
+-      "os"
+-      "strings"
+-
+-      "github.com/0xERR0R/blocky/config"
+-      . "github.com/0xERR0R/blocky/evt"
+-      "github.com/0xERR0R/blocky/lists/parsers"
+-      "github.com/0xERR0R/blocky/log"
+-      "github.com/0xERR0R/blocky/util"
+-      "github.com/sirupsen/logrus"
+-
+-      . "github.com/0xERR0R/blocky/helpertest"
+-      . "github.com/onsi/ginkgo/v2"
+-      . "github.com/onsi/gomega"
+-)
+-
+-var _ = Describe("ListCache", func() {
+-      var (
+-              tmpDir                         *TmpFolder
+-              emptyFile, file1, file2, file3 *TmpFile
+-              server1, server2, server3      *httptest.Server
+-
+-              sut       *ListCache
+-              sutConfig config.SourceLoadingConfig
+-
+-              listCacheType  ListCacheType
+-              lists          map[string][]config.BytesSource
+-              downloader     FileDownloader
+-              mockDownloader *MockDownloader
+-      )
+-
+-      BeforeEach(func() {
+-              var err error
+-
+-              listCacheType = ListCacheTypeBlacklist
+-
+-              sutConfig, err = 
config.WithDefaults[config.SourceLoadingConfig]()
+-              Expect(err).Should(Succeed())
+-
+-              sutConfig.RefreshPeriod = -1
+-
+-              downloader = NewDownloader(config.DownloaderConfig{}, nil)
+-              mockDownloader = nil
+-
+-              server1 = 
TestServer("blocked1.com\nblocked1a.com\n192.168.178.55")
+-              DeferCleanup(server1.Close)
+-              server2 = TestServer("blocked2.com")
+-              DeferCleanup(server2.Close)
+-              server3 = TestServer("blocked3.com\nblocked1a.com")
+-              DeferCleanup(server3.Close)
+-
+-              tmpDir = NewTmpFolder("ListCache")
+-              Expect(tmpDir.Error).Should(Succeed())
+-              DeferCleanup(tmpDir.Clean)
+-
+-              emptyFile = tmpDir.CreateStringFile("empty", "#empty file")
+-              Expect(emptyFile.Error).Should(Succeed())
+-
+-              emptyFile = tmpDir.CreateStringFile("empty", "#empty file")
+-              Expect(emptyFile.Error).Should(Succeed())
+-              file1 = tmpDir.CreateStringFile("file1", "blocked1.com", 
"blocked1a.com")
+-              Expect(file1.Error).Should(Succeed())
+-              file2 = tmpDir.CreateStringFile("file2", "blocked2.com")
+-              Expect(file2.Error).Should(Succeed())
+-              file3 = tmpDir.CreateStringFile("file3", "blocked3.com", 
"blocked1a.com")
+-              Expect(file3.Error).Should(Succeed())
+-      })
+-
+-      JustBeforeEach(func() {
+-              var err error
+-
+-              Expect(lists).ShouldNot(BeNil(), "bad test: forgot to set 
`lists`")
+-
+-              if mockDownloader != nil {
+-                      downloader = mockDownloader
+-              }
+-
+-              sut, err = NewListCache(listCacheType, sutConfig, lists, 
downloader)
+-              Expect(err).Should(Succeed())
+-      })
+-
+-      Describe("List cache and matching", func() {
+-              When("List is empty", func() {
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr0": 
config.NewBytesSources(emptyFile.Path),
+-                              }
+-                      })
+-
+-                      When("Query with empty", func() {
+-                              It("should not panic", func() {
+-                                      group := sut.Match("", []string{"gr0"})
+-                                      Expect(group).Should(BeEmpty())
+-                              })
+-                      })
+-
+-                      It("should not match anything", func() {
+-                              group := sut.Match("google.com", 
[]string{"gr1"})
+-                              Expect(group).Should(BeEmpty())
+-                      })
+-              })
+-              When("List becomes empty on refresh", func() {
+-                      BeforeEach(func() {
+-                              mockDownloader = newMockDownloader(func(res 
chan<- string, err chan<- error) {
+-                                      res <- "blocked1.com"
+-                                      res <- "# nothing"
+-                              })
+-
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": {mockDownloader.ListSource()},
+-                              }
+-                      })
+-
+-                      It("should delete existing elements from group cache", 
func(ctx context.Context) {
+-                              group := sut.Match("blocked1.com", 
[]string{"gr1"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-
+-                              err := sut.refresh(ctx)
+-                              Expect(err).Should(Succeed())
+-
+-                              group = sut.Match("blocked1.com", 
[]string{"gr1"})
+-                              Expect(group).Should(BeEmpty())
+-                      })
+-              })
+-              When("List has invalid lines", func() {
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": {
+-                                              config.TextBytesSource(
+-                                                      "inlinedomain1.com",
+-                                                      "invaliddomain!",
+-                                                      "inlinedomain2.com",
+-                                              ),
+-                                      },
+-                              }
+-                      })
+-
+-                      It("should still other domains", func() {
+-                              group := sut.Match("inlinedomain1.com", 
[]string{"gr1"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-
+-                              group = sut.Match("inlinedomain2.com", 
[]string{"gr1"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-                      })
+-              })
+-              When("a temporary/transient err occurs on download", func() {
+-                      BeforeEach(func() {
+-                              // should produce a transient error on second 
and third attempt
+-                              mockDownloader = newMockDownloader(func(res 
chan<- string, err chan<- error) {
+-                                      res <- "blocked1.com\nblocked2.com\n"
+-                                      err <- &TransientError{inner: 
errors.New("boom")}
+-                                      err <- &TransientError{inner: 
errors.New("boom")}
+-                              })
+-
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": {mockDownloader.ListSource()},
+-                              }
+-                      })
+-
+-                      It("should not delete existing elements from group 
cache", func(ctx context.Context) {
+-                              By("Lists loaded without timeout", func() {
+-                                      Eventually(func(g Gomega) {
+-                                              group := 
sut.Match("blocked1.com", []string{"gr1"})
+-                                              
g.Expect(group).Should(ContainElement("gr1"))
+-                                      }, "1s").Should(Succeed())
+-                              })
+-
+-                              Expect(sut.refresh(ctx)).Should(HaveOccurred())
+-
+-                              By("List couldn't be loaded due to timeout", 
func() {
+-                                      group := sut.Match("blocked1.com", 
[]string{"gr1"})
+-                                      
Expect(group).Should(ContainElement("gr1"))
+-                              })
+-
+-                              sut.Refresh()
+-
+-                              By("List couldn't be loaded due to timeout", 
func() {
+-                                      group := sut.Match("blocked1.com", 
[]string{"gr1"})
+-                                      
Expect(group).Should(ContainElement("gr1"))
+-                              })
+-                      })
+-              })
+-              When("non transient err occurs on download", func() {
+-                      BeforeEach(func() {
+-                              // should produce a non transient error on 
second attempt
+-                              mockDownloader = newMockDownloader(func(res 
chan<- string, err chan<- error) {
+-                                      res <- "blocked1.com"
+-                                      err <- errors.New("boom")
+-                              })
+-
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": {mockDownloader.ListSource()},
+-                              }
+-                      })
+-
+-                      It("should keep existing elements from group cache", 
func(ctx context.Context) {
+-                              By("Lists loaded without err", func() {
+-                                      group := sut.Match("blocked1.com", 
[]string{"gr1"})
+-                                      
Expect(group).Should(ContainElement("gr1"))
+-                              })
+-
+-                              Expect(sut.refresh(ctx)).Should(HaveOccurred())
+-
+-                              By("Lists from first load is kept", func() {
+-                                      group := sut.Match("blocked1.com", 
[]string{"gr1"})
+-                                      
Expect(group).Should(ContainElement("gr1"))
+-                              })
+-                      })
+-              })
+-              When("Configuration has 3 external working urls", func() {
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": 
config.NewBytesSources(server1.URL, server2.URL),
+-                                      "gr2": 
config.NewBytesSources(server3.URL),
+-                              }
+-                      })
+-
+-                      It("should download the list and match against", func() 
{
+-                              group := sut.Match("blocked1.com", 
[]string{"gr1", "gr2"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-
+-                              group = sut.Match("blocked1a.com", 
[]string{"gr1", "gr2"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-
+-                              group = sut.Match("blocked1a.com", 
[]string{"gr2"})
+-                              Expect(group).Should(ContainElement("gr2"))
+-                      })
+-              })
+-              When("Configuration has some faulty urls", func() {
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": 
config.NewBytesSources(server1.URL, server2.URL, "doesnotexist"),
+-                                      "gr2": 
config.NewBytesSources(server3.URL, "someotherfile"),
+-                              }
+-                      })
+-
+-                      It("should download the list and match against", func() 
{
+-                              group := sut.Match("blocked1.com", 
[]string{"gr1", "gr2"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-
+-                              group = sut.Match("blocked1a.com", 
[]string{"gr1", "gr2"})
+-                              Expect(group).Should(ContainElements("gr1", 
"gr2"))
+-
+-                              group = sut.Match("blocked1a.com", 
[]string{"gr2"})
+-                              Expect(group).Should(ContainElement("gr2"))
+-                      })
+-              })
+-              When("List will be updated", func() {
+-                      resultCnt := 0
+-
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": 
config.NewBytesSources(server1.URL),
+-                              }
+-
+-                              _ = 
Bus().SubscribeOnce(BlockingCacheGroupChanged, func(listType ListCacheType, 
group string, cnt int) {
+-                                      resultCnt = cnt
+-                              })
+-                      })
+-
+-                      It("event should be fired and contain count of elements 
in downloaded lists", func() {
+-                              group := sut.Match("blocked1.com", []string{})
+-                              Expect(group).Should(BeEmpty())
+-                              Expect(resultCnt).Should(Equal(3))
+-                      })
+-              })
+-              When("multiple groups are passed", func() {
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": 
config.NewBytesSources(file1.Path, file2.Path),
+-                                      "gr2": config.NewBytesSources("file://" 
+ file3.Path),
+-                              }
+-                      })
+-
+-                      It("should match", func() {
+-                              
Expect(sut.groupedCache.ElementCount("gr1")).Should(Equal(3))
+-                              
Expect(sut.groupedCache.ElementCount("gr2")).Should(Equal(2))
+-
+-                              group := sut.Match("blocked1.com", 
[]string{"gr1", "gr2"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-
+-                              group = sut.Match("blocked1a.com", 
[]string{"gr1", "gr2"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-
+-                              group = sut.Match("blocked1a.com", 
[]string{"gr2"})
+-                              Expect(group).Should(ContainElement("gr2"))
+-                      })
+-              })
+-              When("group with bigger files", func() {
+-                      It("should match", func() {
+-                              file1, lines1 := 
createTestListFile(GinkgoT().TempDir(), 10000)
+-                              file2, lines2 := 
createTestListFile(GinkgoT().TempDir(), 15000)
+-                              file3, lines3 := 
createTestListFile(GinkgoT().TempDir(), 13000)
+-                              lists := map[string][]config.BytesSource{
+-                                      "gr1": config.NewBytesSources(file1, 
file2, file3),
+-                              }
+-
+-                              sut, err := 
NewListCache(ListCacheTypeBlacklist, sutConfig, lists, downloader)
+-                              Expect(err).Should(Succeed())
+-
+-                              
Expect(sut.groupedCache.ElementCount("gr1")).Should(Equal(lines1 + lines2 + 
lines3))
+-                      })
+-              })
+-              When("inline list content is defined", func() {
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": {config.TextBytesSource(
+-                                              "inlinedomain1.com",
+-                                              "#some comment",
+-                                              "inlinedomain2.com",
+-                                      )},
+-                              }
+-                      })
+-
+-                      It("should match", func() {
+-                              
Expect(sut.groupedCache.ElementCount("gr1")).Should(Equal(2))
+-                              group := sut.Match("inlinedomain1.com", 
[]string{"gr1"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-
+-                              group = sut.Match("inlinedomain2.com", 
[]string{"gr1"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-                      })
+-              })
+-              When("Text file can't be parsed", func() {
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": {
+-                                              config.TextBytesSource(
+-                                                      "inlinedomain1.com",
+-                                                      
"lineTooLong"+strings.Repeat("x", bufio.MaxScanTokenSize), // too long
+-                                              ),
+-                                      },
+-                              }
+-                      })
+-
+-                      It("should still match already imported strings", 
func() {
+-                              group := sut.Match("inlinedomain1.com", 
[]string{"gr1"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-                      })
+-              })
+-              When("Text file has too many errors", func() {
+-                      BeforeEach(func() {
+-                              sutConfig.MaxErrorsPerSource = 0
+-                              sutConfig.Strategy = 
config.StartStrategyTypeFailOnError
+-                      })
+-                      It("should fail parsing", func() {
+-                              lists := map[string][]config.BytesSource{
+-                                      "gr1": {
+-                                              
config.TextBytesSource("invaliddomain!"), // too many errors since 
`maxErrorsPerSource` is 0
+-                                      },
+-                              }
+-
+-                              _, err := NewListCache(ListCacheTypeBlacklist, 
sutConfig, lists, downloader)
+-                              Expect(err).ShouldNot(Succeed())
+-                              
Expect(err).Should(MatchError(parsers.ErrTooManyErrors))
+-                      })
+-              })
+-              When("file has end of line comment", func() {
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": 
{config.TextBytesSource("inlinedomain1.com#a comment")},
+-                              }
+-                      })
+-
+-                      It("should still parse the domain", func() {
+-                              group := sut.Match("inlinedomain1.com", 
[]string{"gr1"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-                      })
+-              })
+-              When("inline regex content is defined", func() {
+-                      BeforeEach(func() {
+-                              lists = map[string][]config.BytesSource{
+-                                      "gr1": 
{config.TextBytesSource("/^apple\\.(de|com)$/")},
+-                              }
+-                      })
+-
+-                      It("should match", func() {
+-                              group := sut.Match("apple.com", []string{"gr1"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-
+-                              group = sut.Match("apple.de", []string{"gr1"})
+-                              Expect(group).Should(ContainElement("gr1"))
+-                      })
+-              })
+-      })
+-      Describe("LogConfig", func() {
+-              var (
+-                      logger *logrus.Entry
+-                      hook   *log.MockLoggerHook
+-              )
+-
+-              BeforeEach(func() {
+-                      logger, hook = log.NewMockEntry()
+-              })
+-
+-              It("should print list configuration", func() {
+-                      lists := map[string][]config.BytesSource{
+-                              "gr1": config.NewBytesSources(server1.URL, 
server2.URL),
+-                              "gr2": {config.TextBytesSource("inline", 
"definition")},
+-                      }
+-
+-                      sut, err := NewListCache(ListCacheTypeBlacklist, 
sutConfig, lists, downloader)
+-                      Expect(err).Should(Succeed())
+-
+-                      sut.LogConfig(logger)
+-                      Expect(hook.Calls).ShouldNot(BeEmpty())
+-                      
Expect(hook.Messages).Should(ContainElement(ContainSubstring("gr1:")))
+-                      
Expect(hook.Messages).Should(ContainElement(ContainSubstring("gr2:")))
+-                      
Expect(hook.Messages).Should(ContainElement(ContainSubstring("TOTAL:")))
+-              })
+-      })
+-
+-      Describe("StartStrategy", func() {
+-              When("async load is enabled", func() {
+-                      BeforeEach(func() {
+-                              sutConfig.Strategy = 
config.StartStrategyTypeFast
+-                      })
+-
+-                      It("should never return an error", func() {
+-                              lists := map[string][]config.BytesSource{
+-                                      "gr1": 
config.NewBytesSources("doesnotexist"),
+-                              }
+-
+-                              _, err := NewListCache(ListCacheTypeBlacklist, 
sutConfig, lists, downloader)
+-                              Expect(err).Should(Succeed())
+-                      })
+-              })
+-      })
+-})
+-
+-type MockDownloader struct {
+-      util.MockCallSequence[string]
+-}
+-
+-func newMockDownloader(driver func(res chan<- string, err chan<- error)) 
*MockDownloader {
+-      return &MockDownloader{util.NewMockCallSequence(driver)}
+-}
+-
+-func (m *MockDownloader) DownloadFile(_ string) (io.ReadCloser, error) {
+-      str, err := m.Call()
+-      if err != nil {
+-              return nil, err
+-      }
+-
+-      return io.NopCloser(strings.NewReader(str)), nil
+-}
+-
+-func (m *MockDownloader) ListSource() config.BytesSource {
+-      return config.BytesSource{
+-              Type: config.BytesSourceTypeHttp,
+-              From: "http://mock-downloader";,
+-      }
+-}
+-
+-func createTestListFile(dir string, totalLines int) (string, int) {
+-      file, err := os.CreateTemp(dir, "blocky")
+-      if err != nil {
+-              log.Log().Fatal(err)
+-      }
+-
+-      w := bufio.NewWriter(file)
+-      for i := 0; i < totalLines; i++ {
+-              fmt.Fprintln(w, RandStringBytes(8+rand.Intn(10))+".com")
+-      }
+-      w.Flush()
+-
+-      return file.Name(), totalLines
+-}
+-
+-const (
+-      initCharpool = "abcdefghijklmnopqrstuvwxyz"
+-      contCharpool = initCharpool + "0123456789-"
+-)
+-
+-func RandStringBytes(n int) string {
+-      b := make([]byte, n)
+-
+-      pool := initCharpool
+-
+-      for i := range b {
+-              b[i] = pool[rand.Intn(len(pool))]
+-
+-              pool = contCharpool
+-      }
+-
+-      return string(b)
+-}
+diff --git a/server/server_test.go b/server/server_test.go
+deleted file mode 100644
+index 65b30bc..0000000
+--- a/server/server_test.go
++++ /dev/null
+@@ -1,888 +0,0 @@
+-package server
+-
+-import (
+-      "bytes"
+-      "encoding/base64"
+-      "encoding/json"
+-      "io"
+-      "net"
+-      "net/http"
+-      "strings"
+-      "sync/atomic"
+-      "time"
+-
+-      "github.com/0xERR0R/blocky/api"
+-      "github.com/0xERR0R/blocky/config"
+-      . "github.com/0xERR0R/blocky/helpertest"
+-      . "github.com/0xERR0R/blocky/log"
+-      "github.com/0xERR0R/blocky/model"
+-      "github.com/0xERR0R/blocky/resolver"
+-      "github.com/0xERR0R/blocky/util"
+-      "github.com/creasty/defaults"
+-      . "github.com/onsi/ginkgo/v2"
+-      . "github.com/onsi/gomega"
+-
+-      "github.com/miekg/dns"
+-)
+-
+-var (
+-      mockClientName atomic.Value
+-      sut            *Server
+-      err            error
+-)
+-
+-var _ = BeforeSuite(func() {
+-      var upstreamGoogle, upstreamFritzbox, upstreamClient config.Upstream
+-      googleMockUpstream := 
resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) 
(response *dns.Msg) {
+-              if request.Question[0].Name == "error." {
+-                      return nil
+-              }
+-              response, err := util.NewMsgWithAnswer(
+-                      util.ExtractDomain(request.Question[0]), 123, A, 
"123.124.122.122",
+-              )
+-
+-              Expect(err).Should(Succeed())
+-
+-              return response
+-      })
+-      DeferCleanup(googleMockUpstream.Close)
+-
+-      fritzboxMockUpstream := 
resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) 
(response *dns.Msg) {
+-              response, err := util.NewMsgWithAnswer(
+-                      util.ExtractDomain(request.Question[0]), 3600, A, 
"192.168.178.2",
+-              )
+-
+-              Expect(err).Should(Succeed())
+-
+-              return response
+-      })
+-      DeferCleanup(fritzboxMockUpstream.Close)
+-
+-      clientMockUpstream := 
resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) 
(response *dns.Msg) {
+-              var clientName string
+-              client := mockClientName.Load()
+-
+-              if client != nil {
+-                      clientName = mockClientName.Load().(string)
+-              }
+-
+-              response, err := util.NewMsgWithAnswer(
+-                      util.ExtractDomain(request.Question[0]), 3600, 
dns.Type(dns.TypePTR), clientName,
+-              )
+-
+-              Expect(err).Should(Succeed())
+-
+-              return response
+-      })
+-      DeferCleanup(clientMockUpstream.Close)
+-
+-      upstreamClient = clientMockUpstream.Start()
+-      upstreamFritzbox = fritzboxMockUpstream.Start()
+-      upstreamGoogle = googleMockUpstream.Start()
+-
+-      tmpDir := NewTmpFolder("server")
+-      Expect(tmpDir.Error).Should(Succeed())
+-      DeferCleanup(tmpDir.Clean)
+-
+-      certPem := writeCertPem(tmpDir)
+-      Expect(certPem.Error).Should(Succeed())
+-
+-      keyPem := writeKeyPem(tmpDir)
+-      Expect(keyPem.Error).Should(Succeed())
+-
+-      doubleclickFile := tmpDir.CreateStringFile("doubleclick.net.txt", 
"doubleclick.net", "doubleclick.net.cn")
+-      Expect(doubleclickFile.Error).Should(Succeed())
+-
+-      bildFile := tmpDir.CreateStringFile("www.bild.de.txt", "www.bild.de")
+-      Expect(bildFile.Error).Should(Succeed())
+-
+-      heiseFile := tmpDir.CreateStringFile("heise.de.txt", "heise.de")
+-      Expect(heiseFile.Error).Should(Succeed())
+-
+-      youtubeFile := tmpDir.CreateStringFile("youtube.com.txt", "youtube.com")
+-      Expect(youtubeFile.Error).Should(Succeed())
+-
+-      // create server
+-      sut, err = NewServer(&config.Config{
+-              CustomDNS: config.CustomDNSConfig{
+-                      CustomTTL: config.Duration(3600 * time.Second),
+-                      Mapping: config.CustomDNSMapping{
+-                              HostIPs: map[string][]net.IP{
+-                                      "custom.lan": 
{net.ParseIP("192.168.178.55")},
+-                                      "lan.home":   
{net.ParseIP("192.168.178.56")},
+-                              },
+-                      },
+-              },
+-              Conditional: config.ConditionalUpstreamConfig{
+-                      Mapping: config.ConditionalUpstreamMapping{
+-                              Upstreams: map[string][]config.Upstream{
+-                                      "net.cn":    {upstreamClient},
+-                                      "fritz.box": {upstreamFritzbox},
+-                              },
+-                      },
+-              },
+-              Blocking: config.BlockingConfig{
+-                      BlackLists: map[string][]config.BytesSource{
+-                              "ads": config.NewBytesSources(
+-                                      doubleclickFile.Path,
+-                                      bildFile.Path,
+-                                      heiseFile.Path,
+-                              ),
+-                              "youtube": 
config.NewBytesSources(youtubeFile.Path),
+-                      },
+-                      WhiteLists: map[string][]config.BytesSource{
+-                              "ads":       
config.NewBytesSources(heiseFile.Path),
+-                              "whitelist": 
config.NewBytesSources(heiseFile.Path),
+-                      },
+-                      ClientGroupsBlock: map[string][]string{
+-                              "default":         {"ads"},
+-                              "clWhitelistOnly": {"whitelist"},
+-                              "clAdsAndYoutube": {"ads", "youtube"},
+-                              "clYoutubeOnly":   {"youtube"},
+-                      },
+-                      BlockType: "zeroIp",
+-                      BlockTTL:  config.Duration(6 * time.Hour),
+-              },
+-              Upstreams: config.UpstreamsConfig{
+-                      Groups: map[string][]config.Upstream{"default": 
{upstreamGoogle}},
+-              },
+-              ClientLookup: config.ClientLookupConfig{
+-                      Upstream: upstreamClient,
+-              },
+-
+-              Ports: config.PortsConfig{
+-                      DNS:   config.ListenConfig{"55555"},
+-                      TLS:   config.ListenConfig{"8853"},
+-                      HTTP:  config.ListenConfig{"4000"},
+-                      HTTPS: config.ListenConfig{"4443"},
+-              },
+-              CertFile: certPem.Path,
+-              KeyFile:  keyPem.Path,
+-              Prometheus: config.MetricsConfig{
+-                      Enable: true,
+-                      Path:   "/metrics",
+-              },
+-      })
+-
+-      Expect(err).Should(Succeed())
+-
+-      errChan := make(chan error, 10)
+-
+-      // start server
+-      go func() {
+-              sut.Start(errChan)
+-      }()
+-      DeferCleanup(sut.Stop)
+-
+-      Consistently(errChan, "1s").ShouldNot(Receive())
+-})
+-
+-var _ = Describe("Running DNS server", func() {
+-      Describe("performing DNS request with running server", func() {
+-              BeforeEach(func() {
+-                      mockClientName.Store("")
+-                      // reset client cache
+-                      res := sut.queryResolver
+-                      for res != nil {
+-                              if t, ok := 
res.(*resolver.ClientNamesResolver); ok {
+-                                      t.FlushCache()
+-
+-                                      break
+-                              }
+-                              if c, ok := res.(resolver.ChainedResolver); ok {
+-                                      res = c.GetNext()
+-                              } else {
+-                                      break
+-                              }
+-                      }
+-              })
+-
+-              Context("DNS query is resolvable via external DNS", func() {
+-                      It("should return valid answer", func() {
+-                              
Expect(requestServer(util.NewMsgWithQuestion("google.de.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("google.de.", A, "123.124.122.122"),
+-                                                      
HaveTTL(BeNumerically("==", 123)),
+-                                              ))
+-                      })
+-              })
+-              Context("Custom DNS entry with exact match", func() {
+-                      It("should return valid answer", func() {
+-                              
Expect(requestServer(util.NewMsgWithQuestion("custom.lan.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("custom.lan.", A, "192.168.178.55"),
+-                                                      
HaveTTL(BeNumerically("==", 3600)),
+-                                              ))
+-                      })
+-              })
+-              Context("Custom DNS entry with sub domain", func() {
+-                      It("should return valid answer", func() {
+-                              
Expect(requestServer(util.NewMsgWithQuestion("host.lan.home.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("host.lan.home.", A, "192.168.178.56"),
+-                                                      
HaveTTL(BeNumerically("==", 3600)),
+-                                              ))
+-                      })
+-              })
+-              Context("Conditional upstream", func() {
+-                      It("should resolve query via conditional upstream 
resolver", func() {
+-                              
Expect(requestServer(util.NewMsgWithQuestion("host.fritz.box.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("host.fritz.box.", A, "192.168.178.2"),
+-                                                      
HaveTTL(BeNumerically("==", 3600)),
+-                                              ))
+-                      })
+-              })
+-              Context("Conditional upstream blocking", func() {
+-                      It("Query should be blocked, domain is in default 
group", func() {
+-                              
Expect(requestServer(util.NewMsgWithQuestion("doubleclick.net.cn.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("doubleclick.net.cn.", A, "0.0.0.0"),
+-                                                      
HaveTTL(BeNumerically("==", 21600)),
+-                                              ))
+-                      })
+-              })
+-              Context("Blocking default group", func() {
+-                      It("Query should be blocked, domain is in default 
group", func() {
+-                              
Expect(requestServer(util.NewMsgWithQuestion("doubleclick.net.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("doubleclick.net.", A, "0.0.0.0"),
+-                                                      
HaveTTL(BeNumerically("==", 21600)),
+-                                              ))
+-                      })
+-              })
+-              Context("Blocking default group with sub domain", func() {
+-                      It("Query with subdomain should be blocked, domain is 
in default group", func() {
+-                              
Expect(requestServer(util.NewMsgWithQuestion("www.bild.de.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("www.bild.de.", A, "0.0.0.0"),
+-                                                      
HaveTTL(BeNumerically("==", 21600)),
+-                                              ))
+-                      })
+-              })
+-              Context("no blocking default group with sub domain", func() {
+-                      It("Query with should not be blocked, sub domain is not 
in blacklist", func() {
+-                              
Expect(requestServer(util.NewMsgWithQuestion("bild.de.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      BeDNSRecord("bild.de.", 
A, "123.124.122.122"),
+-                                                      
HaveTTL(BeNumerically("<=", 123)),
+-                                              ))
+-                      })
+-              })
+-              Context("domain is on white and blacklist default group", 
func() {
+-                      It("Query with should not be blocked, domain is on 
white and blacklist", func() {
+-                              
Expect(requestServer(util.NewMsgWithQuestion("heise.de.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("heise.de.", A, "123.124.122.122"),
+-                                                      
HaveTTL(BeNumerically("<=", 123)),
+-                                              ))
+-                      })
+-              })
+-              Context("domain is on client specific white list", func() {
+-                      It("Query with should not be blocked, domain is on 
client's white list", func() {
+-                              mockClientName.Store("clWhitelistOnly")
+-                              
Expect(requestServer(util.NewMsgWithQuestion("heise.de.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("heise.de.", A, "123.124.122.122"),
+-                                                      
HaveTTL(BeNumerically("<=", 123)),
+-                                              ))
+-                      })
+-              })
+-              Context("block client whitelist only", func() {
+-                      It("Query with should be blocked, client has only 
whitelist, domain is not on client's white list", func() {
+-                              mockClientName.Store("clWhitelistOnly")
+-                              
Expect(requestServer(util.NewMsgWithQuestion("google.de.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("google.de.", A, "0.0.0.0"),
+-                                                      
HaveTTL(BeNumerically("==", 21600)),
+-                                              ))
+-                      })
+-              })
+-              Context("block client with 2 groups", func() {
+-                      It("Query with should be blocked, domain is on black 
list", func() {
+-                              mockClientName.Store("clAdsAndYoutube")
+-
+-                              
Expect(requestServer(util.NewMsgWithQuestion("www.bild.de.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("www.bild.de.", A, "0.0.0.0"),
+-                                                      
HaveTTL(BeNumerically("==", 21600)),
+-                                              ))
+-
+-                              
Expect(requestServer(util.NewMsgWithQuestion("youtube.com.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("youtube.com.", A, "0.0.0.0"),
+-                                                      
HaveTTL(BeNumerically("==", 21600)),
+-                                              ))
+-                      })
+-              })
+-              Context("client with 1 group: no block if domain in other 
group", func() {
+-                      It("Query with should not be blocked, domain is on 
black list in another group", func() {
+-                              mockClientName.Store("clYoutubeOnly")
+-
+-                              
Expect(requestServer(util.NewMsgWithQuestion("www.bild.de.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("www.bild.de.", A, "123.124.122.122"),
+-                                                      
HaveTTL(BeNumerically("<=", 123)),
+-                                              ))
+-                      })
+-              })
+-              Context("block client with 1 group", func() {
+-                      It("Query with should not  blocked, domain is on black 
list in client's group", func() {
+-                              mockClientName.Store("clYoutubeOnly")
+-
+-                              
Expect(requestServer(util.NewMsgWithQuestion("youtube.com.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
BeDNSRecord("youtube.com.", A, "0.0.0.0"),
+-                                                      
HaveTTL(BeNumerically("==", 21600)),
+-                                              ))
+-                      })
+-              })
+-              Context("health check", func() {
+-                      It("Should always return dummy response", func() {
+-                              resp := 
requestServer(util.NewMsgWithQuestion("healthcheck.blocky.", A))
+-
+-                              Expect(resp.Answer).Should(BeEmpty())
+-                      })
+-              })
+-      })
+-
+-      Describe("Prometheus endpoint", func() {
+-              When("Prometheus URL is called", func() {
+-                      It("should return prometheus data", func() {
+-                              resp, err := 
http.Get("http://localhost:4000/metrics";)
+-                              Expect(err).Should(Succeed())
+-                              
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
+-                      })
+-              })
+-      })
+-      Describe("Root endpoint", func() {
+-              When("Root URL is called", func() {
+-                      It("should return root page", func() {
+-                              resp, err := http.Get("http://localhost:4000/";)
+-                              Expect(err).Should(Succeed())
+-                              Expect(resp).Should(
+-                                      SatisfyAll(
+-                                              HaveHTTPStatus(http.StatusOK),
+-                                              
HaveHTTPHeaderWithValue("Content-type", "text/html; charset=UTF-8"),
+-                                      ))
+-                      })
+-              })
+-      })
+-
+-      Describe("Query Rest API", func() {
+-              When("Query API is called", func() {
+-                      It("Should process the query", func() {
+-                              req := api.QueryRequest{
+-                                      Query: "google.de",
+-                                      Type:  "A",
+-                              }
+-                              jsonValue, err := json.Marshal(req)
+-                              Expect(err).Should(Succeed())
+-
+-                              resp, err := 
http.Post("http://localhost:4000/api/query";, "application/json", 
bytes.NewBuffer(jsonValue))
+-
+-                              Expect(err).Should(Succeed())
+-                              defer resp.Body.Close()
+-
+-                              Expect(resp).Should(
+-                                      SatisfyAll(
+-                                              HaveHTTPStatus(http.StatusOK),
+-                                              
HaveHTTPHeaderWithValue("Content-type", "application/json"),
+-                                      ))
+-
+-                              var result api.QueryResult
+-                              err = json.NewDecoder(resp.Body).Decode(&result)
+-                              Expect(err).Should(Succeed())
+-                              Expect(result.Response).Should(Equal("A 
(123.124.122.122)"))
+-                      })
+-              })
+-              When("Wrong request type is used", func() {
+-                      It("Should return internal error", func() {
+-                              req := api.QueryRequest{
+-                                      Query: "google.de",
+-                                      Type:  "WrongType",
+-                              }
+-                              jsonValue, err := json.Marshal(req)
+-                              Expect(err).Should(Succeed())
+-
+-                              resp, err := 
http.Post("http://localhost:4000/api/query";, "application/json", 
bytes.NewBuffer(jsonValue))
+-
+-                              Expect(err).Should(Succeed())
+-                              DeferCleanup(resp.Body.Close)
+-
+-                              
Expect(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
+-                      })
+-              })
+-              When("Internal error occurs", func() {
+-                      It("Should return internal error", func() {
+-                              req := api.QueryRequest{
+-                                      Query: "error.",
+-                                      Type:  "A",
+-                              }
+-                              jsonValue, err := json.Marshal(req)
+-                              Expect(err).Should(Succeed())
+-
+-                              resp, err := 
http.Post("http://localhost:4000/api/query";, "application/json", 
bytes.NewBuffer(jsonValue))
+-                              Expect(err).Should(Succeed())
+-                              DeferCleanup(resp.Body.Close)
+-                              
Expect(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
+-                      })
+-              })
+-              When("Request is malformed", func() {
+-                      It("Should return internal error", func() {
+-                              jsonValue := []byte("")
+-
+-                              resp, err := 
http.Post("http://localhost:4000/api/query";, "application/json", 
bytes.NewBuffer(jsonValue))
+-
+-                              Expect(err).Should(Succeed())
+-                              DeferCleanup(resp.Body.Close)
+-
+-                              
Expect(resp.StatusCode).Should(Equal(http.StatusInternalServerError))
+-                      })
+-              })
+-      })
+-
+-      Describe("DOH endpoint", func() {
+-              Context("DOH over GET (RFC 8484)", func() {
+-                      When("DOH get request with 'example.com' is performed", 
func() {
+-                              It("should get a valid response", func() {
+-                                      resp, err := 
http.Get("http://localhost:4000/dns-query?dns=AAABAAABAAAAAAAAA3d3dwdleGFtcGxlA2NvbQAAAQAB";)
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
+-                                      
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", 
"application/dns-message"))
+-
+-                                      rawMsg, err := io.ReadAll(resp.Body)
+-                                      Expect(err).Should(Succeed())
+-
+-                                      msg := new(dns.Msg)
+-                                      err = msg.Unpack(rawMsg)
+-                                      Expect(err).Should(Succeed())
+-
+-                                      
Expect(msg.Answer).Should(BeDNSRecord("www.example.com.", A, "123.124.122.122"))
+-                              })
+-                      })
+-                      When("Request does not contain a valid DNS message", 
func() {
+-                              It("should return 'Bad Request'", func() {
+-                                      resp, err := 
http.Get("http://localhost:4000/dns-query?dns=xxxx";)
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      
Expect(resp).Should(HaveHTTPStatus(http.StatusBadRequest))
+-                              })
+-                      })
+-                      When("Request's parameter does not contain a valid 
base64'", func() {
+-                              It("should return 'Bad Request'", func() {
+-                                      resp, err := 
http.Get("http://localhost:4000/dns-query?dns=äöä";)
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      
Expect(resp).Should(HaveHTTPStatus(http.StatusBadRequest))
+-                              })
+-                      })
+-                      When("Request does not contain a dns parameter", func() 
{
+-                              It("should return 'Bad Request'", func() {
+-                                      resp, err := 
http.Get("http://localhost:4000/dns-query?test";)
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      
Expect(resp).Should(HaveHTTPStatus(http.StatusBadRequest))
+-                              })
+-                      })
+-                      When("Request's dns parameter is too long'", func() {
+-                              It("should return 'URI Too Long'", func() {
+-                                      longBase64msg := 
base64.StdEncoding.EncodeToString([]byte(strings.Repeat("t", 513)))
+-
+-                                      resp, err := 
http.Get("http://localhost:4000/dns-query?dns="; + longBase64msg)
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      
Expect(resp).Should(HaveHTTPStatus(http.StatusRequestURITooLong))
+-                              })
+-                      })
+-              })
+-              Context("DOH over POST (RFC 8484)", func() {
+-                      When("DOH post request with 'example.com' is 
performed", func() {
+-                              It("should get a valid response", func() {
+-                                      msg := 
util.NewMsgWithQuestion("www.example.com.", A)
+-                                      rawDNSMessage, err := msg.Pack()
+-                                      Expect(err).Should(Succeed())
+-
+-                                      resp, err := 
http.Post("http://localhost:4000/dns-query";,
+-                                              "application/dns-message", 
bytes.NewReader(rawDNSMessage))
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      Expect(resp).Should(
+-                                              SatisfyAll(
+-                                                      
HaveHTTPStatus(http.StatusOK),
+-                                                      
HaveHTTPHeaderWithValue("Content-type", "application/dns-message"),
+-                                              ))
+-
+-                                      rawMsg, err := io.ReadAll(resp.Body)
+-                                      Expect(err).Should(Succeed())
+-
+-                                      msg = new(dns.Msg)
+-                                      err = msg.Unpack(rawMsg)
+-                                      Expect(err).Should(Succeed())
+-
+-                                      
Expect(msg.Answer).Should(BeDNSRecord("www.example.com.", A, "123.124.122.122"))
+-                              })
+-                              It("should get a valid response, clientId is 
passed", func() {
+-                                      msg := 
util.NewMsgWithQuestion("www.example.com.", A)
+-                                      rawDNSMessage, err := msg.Pack()
+-                                      Expect(err).Should(Succeed())
+-
+-                                      resp, err := 
http.Post("http://localhost:4000/dns-query/client123";,
+-                                              "application/dns-message", 
bytes.NewReader(rawDNSMessage))
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      Expect(resp).Should(
+-                                              SatisfyAll(
+-                                                      
HaveHTTPStatus(http.StatusOK),
+-                                                      
HaveHTTPHeaderWithValue("Content-type", "application/dns-message"),
+-                                              ))
+-                                      rawMsg, err := io.ReadAll(resp.Body)
+-                                      Expect(err).Should(Succeed())
+-
+-                                      msg = new(dns.Msg)
+-                                      err = msg.Unpack(rawMsg)
+-                                      Expect(err).Should(Succeed())
+-
+-                                      
Expect(msg.Answer).Should(BeDNSRecord("www.example.com.", A, "123.124.122.122"))
+-                              })
+-                      })
+-                      When("POST payload exceeds 512 bytes", func() {
+-                              It("should return 'Payload Too Large'", func() {
+-                                      largeMessage := 
[]byte(strings.Repeat("t", 513))
+-
+-                                      resp, err := 
http.Post("http://localhost:4000/dns-query";, "application/dns-message", 
bytes.NewReader(largeMessage))
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      
Expect(resp).Should(HaveHTTPStatus(http.StatusRequestEntityTooLarge))
+-                              })
+-                      })
+-                      When("Request has wrong type", func() {
+-                              It("should return 'Unsupported Media Type'", 
func() {
+-                                      resp, err := 
http.Post("http://localhost:4000/dns-query";, "application/text", 
bytes.NewReader([]byte("a")))
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      
Expect(resp).Should(HaveHTTPStatus(http.StatusUnsupportedMediaType))
+-                              })
+-                      })
+-                      When("Internal error occurs", func() {
+-                              It("should return 'Internal server error'", 
func() {
+-                                      msg := 
util.NewMsgWithQuestion("error.", A)
+-                                      rawDNSMessage, err := msg.Pack()
+-                                      Expect(err).Should(Succeed())
+-
+-                                      resp, err := 
http.Post("http://localhost:4000/dns-query";,
+-                                              "application/dns-message", 
bytes.NewReader(rawDNSMessage))
+-                                      Expect(err).Should(Succeed())
+-                                      DeferCleanup(resp.Body.Close)
+-
+-                                      
Expect(resp).Should(HaveHTTPStatus(http.StatusInternalServerError))
+-                              })
+-                      })
+-              })
+-      })
+-
+-      Describe("Server create", func() {
+-              var (
+-                      cfg  config.Config
+-                      cErr error
+-              )
+-              BeforeEach(func() {
+-                      cErr = defaults.Set(&cfg)
+-
+-                      Expect(cErr).Should(Succeed())
+-
+-                      cfg.Upstreams.Groups = map[string][]config.Upstream{
+-                              "default": {config.Upstream{Net: 
config.NetProtocolTcpUdp, Host: "1.1.1.1", Port: 53}},
+-                      }
+-
+-                      cfg.Redis.Address = "test-fail"
+-              })
+-              When("Server is created", func() {
+-                      It("is created without redis connection", func() {
+-                              _, err := NewServer(&cfg)
+-
+-                              Expect(err).Should(Succeed())
+-                      })
+-                      It("can't be created if redis server is unavailable", 
func() {
+-                              cfg.Redis.Required = true
+-
+-                              _, err := NewServer(&cfg)
+-
+-                              Expect(err).ShouldNot(Succeed())
+-                      })
+-              })
+-      })
+-
+-      Describe("Server start", Label("XX"), func() {
+-              When("Server start is called", func() {
+-                      It("start was called 2 times, start should fail", 
func() {
+-                              // create server
+-                              server, err := NewServer(&config.Config{
+-                                      Upstreams: config.UpstreamsConfig{
+-                                              Groups: 
map[string][]config.Upstream{
+-                                                      "default": 
{config.Upstream{Net: config.NetProtocolTcpUdp, Host: "4.4.4.4", Port: 53}},
+-                                              },
+-                                      },
+-                                      CustomDNS: config.CustomDNSConfig{
+-                                              Mapping: 
config.CustomDNSMapping{
+-                                                      HostIPs: 
map[string][]net.IP{
+-                                                              "custom.lan": 
{net.ParseIP("192.168.178.55")},
+-                                                              "lan.home":   
{net.ParseIP("192.168.178.56")},
+-                                                      },
+-                                              },
+-                                      },
+-                                      Blocking: 
config.BlockingConfig{BlockType: "zeroIp"},
+-                                      Ports: config.PortsConfig{
+-                                              DNS: 
config.ListenConfig{":55556"},
+-                                      },
+-                              })
+-
+-                              Expect(err).Should(Succeed())
+-
+-                              errChan := make(chan error, 10)
+-
+-                              // start server
+-                              go server.Start(errChan)
+-
+-                              DeferCleanup(server.Stop)
+-
+-                              Consistently(errChan, "1s").ShouldNot(Receive())
+-
+-                              // start again -> should fail
+-                              server.Start(errChan)
+-
+-                              Eventually(errChan).Should(Receive())
+-                      })
+-              })
+-      })
+-      Describe("Server stop", func() {
+-              When("Stop is called", func() {
+-                      It("stop was called 2 times, start should fail", func() 
{
+-                              // create server
+-                              server, err := NewServer(&config.Config{
+-                                      Upstreams: config.UpstreamsConfig{
+-                                              Groups: 
map[string][]config.Upstream{
+-                                                      "default": 
{config.Upstream{Net: config.NetProtocolTcpUdp, Host: "4.4.4.4", Port: 53}},
+-                                              },
+-                                      },
+-                                      CustomDNS: config.CustomDNSConfig{
+-                                              Mapping: 
config.CustomDNSMapping{
+-                                                      HostIPs: 
map[string][]net.IP{
+-                                                              "custom.lan": 
{net.ParseIP("192.168.178.55")},
+-                                                              "lan.home":   
{net.ParseIP("192.168.178.56")},
+-                                                      },
+-                                              },
+-                                      },
+-                                      Blocking: 
config.BlockingConfig{BlockType: "zeroIp"},
+-                                      Ports: config.PortsConfig{
+-                                              DNS: 
config.ListenConfig{"127.0.0.1:55557"},
+-                                      },
+-                              })
+-
+-                              Expect(err).Should(Succeed())
+-
+-                              errChan := make(chan error, 10)
+-
+-                              // start server
+-                              go func() {
+-                                      server.Start(errChan)
+-                              }()
+-
+-                              time.Sleep(100 * time.Millisecond)
+-
+-                              err = server.Stop()
+-
+-                              // stop server, should be ok
+-                              Expect(err).Should(Succeed())
+-
+-                              // stop again, should raise error
+-                              err = server.Stop()
+-
+-                              Expect(err).Should(HaveOccurred())
+-                      })
+-              })
+-      })
+-
+-      Describe("NewServer with strict upstream strategy", func() {
+-              It("successfully returns upstream branches", func() {
+-                      branches, err := createUpstreamBranches(&config.Config{
+-                              Upstreams: config.UpstreamsConfig{
+-                                      Strategy: config.UpstreamStrategyStrict,
+-                                      Groups: config.UpstreamGroups{
+-                                              "default": {{Host: "0.0.0.0"}},
+-                                      },
+-                              },
+-                      },
+-                              nil)
+-
+-                      Expect(err).ToNot(HaveOccurred())
+-                      Expect(branches).ToNot(BeNil())
+-                      Expect(branches).To(HaveLen(1))
+-                      _ = branches["default"].(*resolver.StrictResolver)
+-              })
+-      })
+-
+-      Describe("create query resolver", func() {
+-              When("some upstream returns error", func() {
+-                      It("create query resolver should return error", func() {
+-                              r, err := createQueryResolver(&config.Config{
+-                                      StartVerifyUpstream: true,
+-                                      Upstreams: config.UpstreamsConfig{
+-                                              Groups: config.UpstreamGroups{
+-                                                      "default": {{Host: 
"0.0.0.0"}},
+-                                              },
+-                                      },
+-                              },
+-                                      nil, nil)
+-
+-                              Expect(err).To(HaveOccurred())
+-                              
Expect(err).To(MatchError(ContainSubstring("creation of upstream branches 
failed: ")))
+-                              Expect(r).To(BeNil())
+-                      })
+-              })
+-      })
+-
+-      Describe("resolve client IP", func() {
+-              Context("UDP address", func() {
+-                      It("should correct resolve client IP", func() {
+-                              ip, protocol := 
resolveClientIPAndProtocol(&net.UDPAddr{IP: net.ParseIP("192.168.178.88")})
+-                              
Expect(ip).Should(Equal(net.ParseIP("192.168.178.88")))
+-                              
Expect(protocol).Should(Equal(model.RequestProtocolUDP))
+-                      })
+-              })
+-              Context("TCP address", func() {
+-                      It("should correct resolve client IP", func() {
+-                              ip, protocol := 
resolveClientIPAndProtocol(&net.TCPAddr{IP: net.ParseIP("192.168.178.88")})
+-                              
Expect(ip).Should(Equal(net.ParseIP("192.168.178.88")))
+-                              
Expect(protocol).Should(Equal(model.RequestProtocolTCP))
+-                      })
+-              })
+-      })
+-
+-      Describe("self-signed certificate creation", func() {
+-              var (
+-                      cfg  config.Config
+-                      cErr error
+-              )
+-              BeforeEach(func() {
+-                      cErr = defaults.Set(&cfg)
+-
+-                      Expect(cErr).Should(Succeed())
+-
+-                      cfg.Upstreams.Groups = map[string][]config.Upstream{
+-                              "default": {config.Upstream{Net: 
config.NetProtocolTcpUdp, Host: "1.1.1.1", Port: 53}},
+-                      }
+-              })
+-
+-              It("should create self-signed certificate if key/cert files are 
not provided", func() {
+-                      cfg.KeyFile = ""
+-                      cfg.CertFile = ""
+-                      cfg.Ports = config.PortsConfig{
+-                              HTTPS: []string{":14443"},
+-                      }
+-
+-                      sut, err := NewServer(&cfg)
+-                      Expect(err).Should(Succeed())
+-                      Expect(sut.cert.Certificate).ShouldNot(BeNil())
+-              })
+-      })
+-})
+-
+-func requestServer(request *dns.Msg) *dns.Msg {
+-      conn, err := net.Dial("udp", ":55555")
+-      if err != nil {
+-              Log().Fatal("could not connect to server: ", err)
+-      }
+-      defer conn.Close()
+-
+-      msg, err := request.Pack()
+-      if err != nil {
+-              Log().Fatal("can't pack request: ", err)
+-      }
+-
+-      _, err = conn.Write(msg)
+-      if err != nil {
+-              Log().Fatal("can't send request to server: ", err)
+-      }
+-
+-      out := make([]byte, 1024)
+-
+-      if _, err := conn.Read(out); err == nil {
+-              response := new(dns.Msg)
+-
+-              err = response.Unpack(out)
+-
+-              if err != nil {
+-                      Log().Fatal("can't unpack response: ", err)
+-              }
+-
+-              return response
+-      }
+-
+-      Log().Fatal("could not read from connection", err)
+-
+-      return nil
+-}
+-
+-func writeCertPem(tmpDir *TmpFolder) *TmpFile {
+-      return tmpDir.CreateStringFile("cert.pem",
+-              "-----BEGIN CERTIFICATE-----",
+-              
"MIICMzCCAZygAwIBAgIRAJCCrDTGEtZfRpxDY1KAoswwDQYJKoZIhvcNAQELBQAw",
+-              
"EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2",
+-              
"MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw",
+-              
"gYkCgYEA4mEaF5yWYYrTfMgRXdBpgGnqsHIADQWlw7BIJWD/gNp+fgp4TUZ/7ggV",
+-              
"rrvRORvRFjw14avd9L9EFP7XLi8ViU3uoE1UWI32MlrKqLbGNCXyUIApIoqlbRg6",
+-              
"iErxIk5+ChzFuysQOx01S2yv/ML6dx7NOGHs1S38MUzRZtcXBH8CAwEAAaOBhjCB",
+-              
"gzAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/",
+-              
"BAUwAwEB/zAdBgNVHQ4EFgQUslNI6tYIv909RttHaZVMS/u/VYYwLAYDVR0RBCUw",
+-              
"I4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEB",
+-              
"CwUAA4GBAJ2gRpQHr5Qj7dt26bYVMdN4JGXTsvjbVrJfKI0VfPGJ+SUY/uTVBUeX",
+-              
"+Cwv4DFEPBlNx/lzuUkwmRaExC4/w81LWwxe5KltYsjyJuYowiUbLZ6tzLaQ9Bcx",
+-              "jxClAVvgj90TGYOwsv6ESOX7GWteN1FlD3+jk7vefjFagaKKFYR9",
+-              "-----END CERTIFICATE-----")
+-}
+-
+-func writeKeyPem(tmpDir *TmpFolder) *TmpFile {
+-      return tmpDir.CreateStringFile("key.pem",
+-              "-----BEGIN PRIVATE KEY-----",
+-              
"MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOJhGheclmGK03zI",
+-              
"EV3QaYBp6rByAA0FpcOwSCVg/4Dafn4KeE1Gf+4IFa670Tkb0RY8NeGr3fS/RBT+",
+-              
"1y4vFYlN7qBNVFiN9jJayqi2xjQl8lCAKSKKpW0YOohK8SJOfgocxbsrEDsdNUts",
+-              
"r/zC+ncezThh7NUt/DFM0WbXFwR/AgMBAAECgYEA1exixstPhI+2+OTrHFc1S4dL",
+-              
"oz+ncqbSlZEBLGl0KWTQQfVM5+FmRR7Yto1/0lLKDBQL6t0J2x3fjWOhHmCaHKZA",
+-              
"VAvZ8+OKxwofih3hlO0tGCB8szUJygp2FAmd0rOUqvPQ+PTohZEUXyDaB8MOIbX+",
+-              
"qoo7g19+VlbyKqmM8HkCQQDs4GQJwEn7GXKllSMyOfiYnjQM2pwsqO0GivXkH+p3",
+-              
"+h5KDp4g3O4EbmbrvZyZB2euVsBjW3pFMu+xPXuOXf91AkEA9KfC7LGLD2OtLmrM",
+-              
"iCZAqHlame+uEEDduDmqjTPnNKUWVeRtYKMF5Hltbeo1jMXMSbVZ+fRWKfQ+HAhQ",
+-              
"xjFJowJAV6U7PqRoe0FSO1QwXrA2fHnk9nCY4qlqckZObyckAVqJhIteFPjKFNeo",
+-              
"u0dAPxsPUOGGc/zwA9Sx/ZmrMuUy1QJBALl7bqawO/Ng6G0mfwZBqgeQaYYHVnnw",
+-              
"E6iV353J2eHpvzNDSUFYlyEOhk4soIindSf0m9CK08Be8a+jBkocF+0CQQC+Hi7L",
+-              
"kZV1slpW82BxYIhs9Gb0OQgK8SsI4aQPTFGUarQXXAm4eRqBO0kaG+jGX6TtW353",
+-              "EHK784GIxwVXKej/",
+-              "-----END PRIVATE KEY-----")
+-}
+diff --git a/resolver/hosts_file_resolver_test.go 
b/resolver/hosts_file_resolver_test.go
+deleted file mode 100644
+index eef4591..0000000
+--- a/resolver/hosts_file_resolver_test.go
++++ /dev/null
+@@ -1,376 +0,0 @@
+-package resolver
+-
+-import (
+-      "context"
+-      "time"
+-
+-      "github.com/0xERR0R/blocky/config"
+-      . "github.com/0xERR0R/blocky/helpertest"
+-      "github.com/0xERR0R/blocky/log"
+-      . "github.com/0xERR0R/blocky/model"
+-      "github.com/miekg/dns"
+-      . "github.com/onsi/ginkgo/v2"
+-      . "github.com/onsi/gomega"
+-      "github.com/stretchr/testify/mock"
+-)
+-
+-var _ = Describe("HostsFileResolver", func() {
+-      var (
+-              TTL = uint32(time.Now().Second())
+-
+-              sut       *HostsFileResolver
+-              sutConfig config.HostsFileConfig
+-              m         *mockResolver
+-              tmpDir    *TmpFolder
+-              tmpFile   *TmpFile
+-      )
+-
+-      Describe("Type", func() {
+-              It("follows conventions", func() {
+-                      expectValidResolverType(sut)
+-              })
+-      })
+-
+-      BeforeEach(func() {
+-              tmpDir = NewTmpFolder("HostsFileResolver")
+-              Expect(tmpDir.Error).Should(Succeed())
+-              DeferCleanup(tmpDir.Clean)
+-
+-              tmpFile = writeHostFile(tmpDir)
+-              Expect(tmpFile.Error).Should(Succeed())
+-
+-              sutConfig = config.HostsFileConfig{
+-                      Sources:        config.NewBytesSources(tmpFile.Path),
+-                      HostsTTL:       config.Duration(time.Duration(TTL) * 
time.Second),
+-                      FilterLoopback: true,
+-                      Loading: config.SourceLoadingConfig{
+-                              RefreshPeriod:      -1,
+-                              MaxErrorsPerSource: 5,
+-                      },
+-              }
+-      })
+-
+-      JustBeforeEach(func() {
+-              var err error
+-
+-              sut, err = NewHostsFileResolver(sutConfig, 
systemResolverBootstrap)
+-              Expect(err).Should(Succeed())
+-
+-              m = &mockResolver{}
+-              m.On("Resolve", mock.Anything).Return(&Response{Res: 
new(dns.Msg)}, nil)
+-              sut.Next(m)
+-      })
+-
+-      Describe("IsEnabled", func() {
+-              It("is true", func() {
+-                      Expect(sut.IsEnabled()).Should(BeTrue())
+-              })
+-      })
+-
+-      Describe("LogConfig", func() {
+-              It("should log something", func() {
+-                      logger, hook := log.NewMockEntry()
+-
+-                      sut.LogConfig(logger)
+-
+-                      Expect(hook.Calls).ShouldNot(BeEmpty())
+-              })
+-      })
+-
+-      Describe("Using hosts file", func() {
+-              When("Hosts file cannot be located", func() {
+-                      BeforeEach(func() {
+-                              sutConfig = config.HostsFileConfig{
+-                                      Sources:  
config.NewBytesSources("/this/file/does/not/exist"),
+-                                      HostsTTL: 
config.Duration(time.Duration(TTL) * time.Second),
+-                              }
+-                      })
+-                      It("should not parse any hosts", func() {
+-                              Expect(sut.cfg.Sources).ShouldNot(BeEmpty())
+-                              Expect(sut.hosts.v4.hosts).Should(BeEmpty())
+-                              Expect(sut.hosts.v6.hosts).Should(BeEmpty())
+-                              Expect(sut.hosts.v4.aliases).Should(BeEmpty())
+-                              Expect(sut.hosts.v6.aliases).Should(BeEmpty())
+-                              Expect(sut.hosts.isEmpty()).Should(BeTrue())
+-                      })
+-                      It("should go to next resolver on query", func() {
+-                              Expect(sut.Resolve(newRequest("example.com.", 
A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
HaveResponseType(ResponseTypeRESOLVED),
+-                                                      
HaveReturnCode(dns.RcodeSuccess),
+-                                              ))
+-                              m.AssertExpectations(GinkgoT())
+-                      })
+-              })
+-
+-              When("Hosts file is not set", func() {
+-                      BeforeEach(func() {
+-                              sutConfig.Deprecated.Filepath = 
new(config.BytesSource)
+-                              sutConfig.Sources = nil
+-
+-                              m = &mockResolver{}
+-                              m.On("Resolve", 
mock.Anything).Return(&Response{Res: new(dns.Msg)}, nil)
+-                              sut.Next(m)
+-                      })
+-                      It("should not return an error", func() {
+-                              err := sut.loadSources(context.Background())
+-                              Expect(err).Should(Succeed())
+-                      })
+-                      It("should go to next resolver on query", func() {
+-                              Expect(sut.Resolve(newRequest("example.com.", 
A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
HaveResponseType(ResponseTypeRESOLVED),
+-                                                      
HaveReturnCode(dns.RcodeSuccess),
+-                                              ))
+-                              m.AssertExpectations(GinkgoT())
+-                      })
+-              })
+-
+-              When("Hosts file can be located", func() {
+-                      It("should parse it successfully", func() {
+-                              Expect(sut).ShouldNot(BeNil())
+-                              Expect(sut.hosts.v4.hosts).Should(HaveLen(5))
+-                              Expect(sut.hosts.v6.hosts).Should(HaveLen(2))
+-                              Expect(sut.hosts.v4.aliases).Should(HaveLen(4))
+-                              Expect(sut.hosts.v6.aliases).Should(HaveLen(2))
+-                      })
+-
+-                      When("filterLoopback is false", func() {
+-                              BeforeEach(func() {
+-                                      sutConfig.FilterLoopback = false
+-                              })
+-
+-                              It("should parse it successfully", func() {
+-                                      Expect(sut).ShouldNot(BeNil())
+-                                      
Expect(sut.hosts.v4.hosts).Should(HaveLen(7))
+-                                      
Expect(sut.hosts.v6.hosts).Should(HaveLen(3))
+-                                      
Expect(sut.hosts.v4.aliases).Should(HaveLen(5))
+-                                      
Expect(sut.hosts.v6.aliases).Should(HaveLen(2))
+-                              })
+-                      })
+-              })
+-
+-              When("Hosts file has too many errors", func() {
+-                      BeforeEach(func() {
+-                              tmpFile = 
tmpDir.CreateStringFile("hosts-too-many-errors.txt",
+-                                      "invalidip localhost",
+-                                      "127.0.0.1 localhost", // ok
+-                                      "127.0.0.1 # no host",
+-                                      "127.0.0.1 invalidhost!",
+-                                      "a.b.c.d localhost",
+-                                      "127.0.0.x localhost",
+-                                      "256.0.0.1 localhost",
+-                              )
+-                              Expect(tmpFile.Error).Should(Succeed())
+-
+-                              sutConfig.Sources = 
config.NewBytesSources(tmpFile.Path)
+-                      })
+-
+-                      It("should not be used", func() {
+-                              Expect(sut).ShouldNot(BeNil())
+-                              Expect(sut.cfg.Sources).ShouldNot(BeEmpty())
+-                              Expect(sut.hosts.v4.hosts).Should(BeEmpty())
+-                              Expect(sut.hosts.v6.hosts).Should(BeEmpty())
+-                              Expect(sut.hosts.v4.aliases).Should(BeEmpty())
+-                              Expect(sut.hosts.v6.aliases).Should(BeEmpty())
+-                      })
+-              })
+-
+-              When("IPv4 mapping is defined for a host", func() {
+-                      It("defined ipv4 query should be resolved", func() {
+-                              Expect(sut.Resolve(newRequest("ipv4host.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
HaveResponseType(ResponseTypeHOSTSFILE),
+-                                                      
HaveReturnCode(dns.RcodeSuccess),
+-                                                      
BeDNSRecord("ipv4host.", A, "192.168.2.1"),
+-                                                      
HaveTTL(BeNumerically("==", TTL)),
+-                                              ))
+-                      })
+-                      It("defined ipv4 query for alias should be resolved", 
func() {
+-                              Expect(sut.Resolve(newRequest("router2.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
HaveResponseType(ResponseTypeHOSTSFILE),
+-                                                      
HaveReturnCode(dns.RcodeSuccess),
+-                                                      BeDNSRecord("router2.", 
A, "10.0.0.1"),
+-                                                      
HaveTTL(BeNumerically("==", TTL)),
+-                                              ))
+-                      })
+-                      It("ipv4 query should return NOERROR and empty result", 
func() {
+-                              
Expect(sut.Resolve(newRequest("does.not.exist.", A))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      HaveNoAnswer(),
+-                                                      
HaveReturnCode(dns.RcodeSuccess),
+-                                                      
HaveResponseType(ResponseTypeRESOLVED),
+-                                              ))
+-                      })
+-              })
+-
+-              When("IPv6 mapping is defined for a host", func() {
+-                      It("defined ipv6 query should be resolved", func() {
+-                              Expect(sut.Resolve(newRequest("ipv6host.", 
AAAA))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      
HaveResponseType(ResponseTypeHOSTSFILE),
+-                                                      
HaveReturnCode(dns.RcodeSuccess),
+-                                                      
BeDNSRecord("ipv6host.", AAAA, "faaf:faaf:faaf:faaf::1"),
+-                                                      
HaveTTL(BeNumerically("==", TTL)),
+-                                              ))
+-                      })
+-                      It("ipv6 query should return NOERROR and empty result", 
func() {
+-                              
Expect(sut.Resolve(newRequest("does.not.exist.", AAAA))).
+-                                      Should(
+-                                              SatisfyAll(
+-                                                      HaveNoAnswer(),
+-                                                      
HaveReturnCode(dns.RcodeSuccess),
+-                                                      
HaveResponseType(ResponseTypeRESOLVED),
+-                                              ))
+-                      })
+-              })
+-
+-              When("the domain is not known", func() {
+-                      It("calls the next resolver", func() {
+-                              resp, err := 
sut.Resolve(newRequest("not-in-hostsfile.tld.", A))
+-                              Expect(err).Should(Succeed())
+-                              
Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
+-                              m.AssertExpectations(GinkgoT())
+-                      })
+-              })
+-
+-              When("the question type is not handled", func() {
+-                      It("calls the next resolver", func() {
+-                              resp, err := 
sut.Resolve(newRequest("localhost.", MX))
+-                              Expect(err).Should(Succeed())
+-                              
Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
+-                              m.AssertExpectations(GinkgoT())
+-                      })
+-              })
+-
+-              When("Reverse DNS request is received", func() {
+-                      It("should resolve the defined domain name", func() {
+-                              By("ipv4 with one hostname", func() {
+-                                      
Expect(sut.Resolve(newRequest("2.0.0.10.in-addr.arpa.", PTR))).
+-                                              Should(
+-                                                      SatisfyAll(
+-                                                              
HaveResponseType(ResponseTypeHOSTSFILE),
+-                                                              
HaveReturnCode(dns.RcodeSuccess),
+-                                                              
BeDNSRecord("2.0.0.10.in-addr.arpa.", PTR, "router3."),
+-                                                              
HaveTTL(BeNumerically("==", TTL)),
+-                                                      ))
+-                              })
+-                              By("ipv4 with aliases", func() {
+-                                      
Expect(sut.Resolve(newRequest("1.0.0.10.in-addr.arpa.", PTR))).
+-                                              Should(
+-                                                      SatisfyAll(
+-                                                              
HaveResponseType(ResponseTypeHOSTSFILE),
+-                                                              
HaveReturnCode(dns.RcodeSuccess),
+-                                                              
WithTransform(ToAnswer, ContainElements(
+-                                                                      
BeDNSRecord("1.0.0.10.in-addr.arpa.", PTR, "router0."),
+-                                                                      
BeDNSRecord("1.0.0.10.in-addr.arpa.", PTR, "router1."),
+-                                                                      
BeDNSRecord("1.0.0.10.in-addr.arpa.", PTR, "router2."),
+-                                                              )),
+-                                                      ))
+-                              })
+-                              By("ipv6", func() {
+-                                      
Expect(sut.Resolve(newRequest("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.a.a.f.f.a.a.f.f.a.a.f.f.a.a.f.ip6.arpa.",
 PTR))).
+-                                              Should(
+-                                                      SatisfyAll(
+-                                                              
HaveResponseType(ResponseTypeHOSTSFILE),
+-                                                              
HaveReturnCode(dns.RcodeSuccess),
+-                                                              
WithTransform(ToAnswer, ContainElements(
+-                                                                      
BeDNSRecord("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.a.a.f.f.a.a.f.f.a.a.f.f.a.a.f.ip6.arpa.",
+-                                                                              
PTR, "ipv6host."),
+-                                                                      
BeDNSRecord("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.a.a.f.f.a.a.f.f.a.a.f.f.a.a.f.ip6.arpa.",
+-                                                                              
PTR, "ipv6host.local.lan."),
+-                                                              )),
+-                                                      ))
+-                              })
+-                      })
+-
+-                      It("should ignore invalid PTR", func() {
+-                              resp, err := 
sut.Resolve(newRequest("2.0.0.10.in-addr.fail.arpa.", PTR))
+-                              Expect(err).Should(Succeed())
+-                              
Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
+-                              m.AssertExpectations(GinkgoT())
+-                      })
+-
+-                      When("filterLoopback is true", func() {
+-                              It("calls the next resolver", func() {
+-                                      resp, err := 
sut.Resolve(newRequest("1.0.0.127.in-addr.arpa.", PTR))
+-                                      Expect(err).Should(Succeed())
+-                                      
Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
+-                                      m.AssertExpectations(GinkgoT())
+-                              })
+-                      })
+-
+-                      When("the IP is not known", func() {
+-                              It("calls the next resolver", func() {
+-                                      resp, err := 
sut.Resolve(newRequest("255.255.255.255.in-addr.arpa.", PTR))
+-                                      Expect(err).Should(Succeed())
+-                                      
Expect(resp).ShouldNot(HaveResponseType(ResponseTypeHOSTSFILE))
+-                                      m.AssertExpectations(GinkgoT())
+-                              })
+-                      })
+-
+-                      When("filterLoopback is false", func() {
+-                              BeforeEach(func() {
+-                                      sutConfig.FilterLoopback = false
+-                              })
+-
+-                              It("resolve the defined domain name", func() {
+-                                      
Expect(sut.Resolve(newRequest("1.1.0.127.in-addr.arpa.", PTR))).
+-                                              Should(
+-                                                      SatisfyAll(
+-                                                              
HaveResponseType(ResponseTypeHOSTSFILE),
+-                                                              
HaveReturnCode(dns.RcodeSuccess),
+-                                                              
WithTransform(ToAnswer, ContainElements(
+-                                                                      
BeDNSRecord("1.1.0.127.in-addr.arpa.", PTR, "localhost2."),
+-                                                                      
BeDNSRecord("1.1.0.127.in-addr.arpa.", PTR, "localhost2.local.lan."),
+-                                                              )),
+-                                                      ))
+-                              })
+-                      })
+-              })
+-      })
+-
+-      Describe("Delegating to next resolver", func() {
+-              When("no hosts file is provided", func() {
+-                      It("should delegate to next resolver", func() {
+-                              _, err := 
sut.Resolve(newRequest("example.com.", A))
+-                              Expect(err).Should(Succeed())
+-                              // delegate was executed
+-                              m.AssertExpectations(GinkgoT())
+-                      })
+-              })
+-      })
+-})
+-
+-func writeHostFile(tmpDir *TmpFolder) *TmpFile {
+-      return tmpDir.CreateStringFile("hosts.txt",
+-              "# Random comment",
+-              "127.0.0.1               localhost",
+-              "127.0.1.1               localhost2  localhost2.local.lan",
+-              "::1                     localhost",
+-              "# Two empty lines to follow",
+-              "",
+-              "",
+-              "faaf:faaf:faaf:faaf::1  ipv6host    ipv6host.local.lan",
+-              "192.168.2.1             ipv4host    ipv4host.local.lan",
+-              "faaf:faaf:faaf:faaf::2  dualhost    dualhost.local.lan",
+-              "192.168.2.2             dualhost    dualhost.local.lan",
+-              "10.0.0.1                router0 router1 router2",
+-              "10.0.0.2                router3     # Another comment",
+-              "10.0.0.3                router4#comment without a space",
+-              "10.0.0.4                            # Invalid entry",
+-              "300.300.300.300         invalid4    # Invalid IPv4",
+-              "abcd:efgh:ijkl::1       invalid6    # Invalid IPv6",
+-              "1.2.3.4                 localhost", // localhost name but not 
localhost IP
+-
+-              // from 
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
+-              "fe80::1%lo0             localhost", // interface name
+-      )
+-}

diff --git a/net-dns/blocky/metadata.xml b/net-dns/blocky/metadata.xml
new file mode 100644
index 0000000000..e8b50d9eb3
--- /dev/null
+++ b/net-dns/blocky/metadata.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE pkgmetadata SYSTEM "https://www.gentoo.org/dtd/metadata.dtd";>
+<pkgmetadata>
+  <maintainer type="person">
+    <email>me@rahil.rocks</email>
+    <name>Rahil Bhimjiani</name>
+  </maintainer>
+  <longdescription>
+    Blocky is a DNS proxy and ad-blocker alternative to Pi-Hole and AdGuard 
Home written in Go with support for DNSSEC, DNS over HTTPS (DoH), DNS over TLS 
(DoT), metrics via prometheus-grafana, REST api, per client per group 
blacklists and whitelists, custom domains, conditional forwarding and simple 
YAML config.
+  </longdescription>
+  <upstream>
+    <remote-id type="github">0xERR0R/blocky</remote-id>
+    <bugs-to>https://github.com/0xERR0R/blocky/issues</bugs-to>
+    <doc>https://0xerr0r.github.io/blocky/</doc>
+  </upstream>
+</pkgmetadata>

Reply via email to