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
-	)
-}
