Adguardhome/internal/querylog/http_internal_test.go
Fedor Setrakov 9d1110d289 Pull request 2645: AGDNS-3951-querylog-search-criterion
Squashed commit of the following:

commit 8606668738add53456063aa8f68ded3812170df3
Merge: 9aff68363 b60af4bf3
Author: f.setrakov <f.setrakov@adguard.com>
Date:   Wed May 13 11:45:39 2026 +0300

    Merge branch 'master' into AGDNS-3951-querylog-search-criterion

commit 9aff6836320a8313900101311adaa28ac826c53e
Author: f.setrakov <f.setrakov@adguard.com>
Date:   Fri May 8 17:40:09 2026 +0300

    querylog: rm unicode check

commit b093974b08cf02ea42baf492690ad64c93879fa5
Author: f.setrakov <f.setrakov@adguard.com>
Date:   Fri May 8 16:12:41 2026 +0300

    all: imp code

commit 2b2c35708ba009e2e93fdd46674311b976c1a123
Author: f.setrakov <f.setrakov@adguard.com>
Date:   Thu May 7 16:39:20 2026 +0300

    all: imp docs, tests

commit c1d1fd8294972f66e4333c44309ca65e1a4495d7
Author: f.setrakov <f.setrakov@adguard.com>
Date:   Wed May 6 15:16:30 2026 +0300

    all: imp docs, quick match

commit 2cb80fd6b7a7bb2e6eae359b3596f24299c4c817
Merge: 17fac082d e5c79502f
Author: f.setrakov <f.setrakov@adguard.com>
Date:   Tue May 5 12:56:41 2026 +0300

    Merge branch 'master' into AGDNS-3951-querylog-search-criterion

commit 17fac082d98b31a3b31e915496a75b4be85102a0
Author: f.setrakov <f.setrakov@adguard.com>
Date:   Tue May 5 12:50:51 2026 +0300

    all: imp code

commit 3d093cc9886f0ec5c1ac74fa3e7069edb925963f
Author: f.setrakov <f.setrakov@adguard.com>
Date:   Mon May 4 17:51:45 2026 +0300

    all: add tests

commit 2a33db2182811dbeb24311a72734d9be46d155c6
Author: f.setrakov <f.setrakov@adguard.com>
Date:   Wed Apr 29 19:10:02 2026 +0300

    all: add reason criterion
2026-05-13 09:49:12 +00:00

210 lines
5.1 KiB
Go

package querylog
import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Test domains for querylog search testing.
const (
testDomainBlocked = "blocked.org"
testDomainNotFound = "notfound.org"
testDomainRewritten = "rewritten.org"
)
// response is the GET /control/querylog HTTP response structure.
type response struct {
Data []data `json:"data"`
}
// data is a single querylog entry in the response.
type data struct {
Question question `json:"question"`
}
// question is the DNS question part of a querylog entry.
type question struct {
Name string `json:"name"`
}
// parseHostnamesFromEntry is a helper that parses the /control/querylog
// response and extracts the host names from it.
func parseHostnamesFromEntry(tb testing.TB, in io.Reader) (hostnames []string) {
tb.Helper()
var resp response
err := json.NewDecoder(in).Decode(&resp)
require.NoError(tb, err)
for _, d := range resp.Data {
hostnames = append(hostnames, d.Question.Name)
}
return hostnames
}
func TestQuerylog_HandleQueryLog_reasonSearchCriterion(t *testing.T) {
testCases := []struct {
name string
query url.Values
wantMsg string
wantHosts []string
wantStatus int
}{{
name: "no_params",
query: url.Values{},
wantMsg: "",
wantStatus: http.StatusOK,
wantHosts: []string{testDomainRewritten, testDomainBlocked, testDomainNotFound},
}, {
name: "reason_not_found",
query: url.Values{"reason": []string{filtering.NotFilteredNotFound.String()}},
wantMsg: "",
wantStatus: http.StatusOK,
wantHosts: []string{testDomainNotFound},
}, {
name: "reason_block_list",
query: url.Values{"reason": []string{filtering.FilteredBlockList.String()}},
wantMsg: "",
wantStatus: http.StatusOK,
wantHosts: []string{testDomainBlocked},
}, {
name: "reason_rewritten",
query: url.Values{"reason": []string{filtering.Rewritten.String()}},
wantMsg: "",
wantStatus: http.StatusOK,
wantHosts: []string{testDomainRewritten},
}, {
name: "multiple_reasons",
query: url.Values{"reason": []string{
filtering.Rewritten.String(),
filtering.FilteredBlockList.String(),
}},
wantMsg: "",
wantStatus: http.StatusOK,
wantHosts: []string{testDomainRewritten, testDomainBlocked},
}, {
name: "invalid_reason",
query: url.Values{"reason": []string{"InvalidReason"}},
wantMsg: `parsing params: reason: bad enum value: "InvalidReason"` + "\n",
wantStatus: http.StatusBadRequest,
wantHosts: nil,
}, {
name: "reason_and_status_conflict",
query: url.Values{
"reason": []string{filtering.Rewritten.String()},
"response_status": []string{filteringStatusAll},
},
wantMsg: `parsing params: "reason" and "response_status"` +
` criteria cannot be used together` + "\n",
wantStatus: http.StatusBadRequest,
wantHosts: nil,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
l := newTestQueryLog(t)
u := (&url.URL{
Path: "/control/querylog",
RawQuery: tc.query.Encode(),
}).String()
require.True(t, t.Run("memory", func(t *testing.T) {
testSearchAPI(t, l, tc.wantStatus, tc.wantHosts, tc.wantMsg, u)
}))
require.True(t, t.Run("file", func(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
err := l.flushLogBuffer(ctx)
require.NoError(t, err)
testSearchAPI(t, l, tc.wantStatus, tc.wantHosts, tc.wantMsg, u)
}))
})
}
}
// newTestQueryLog is a helper that returns new *queryLog initialized with
// common test values. It also adds several test entries.
func newTestQueryLog(tb testing.TB) (l *queryLog) {
tb.Helper()
l, err := newQueryLog(Config{
Logger: testLogger,
Enabled: true,
FileEnabled: true,
RotationIvl: timeutil.Day,
MemSize: 100,
BaseDir: tb.TempDir(),
Anonymizer: aghnet.NewIPMut(nil),
})
require.NoError(tb, err)
addTestEntry(
l,
testDomainNotFound,
testAnswerIPv4,
testClientIPv4,
filtering.NotFilteredNotFound,
)
addTestEntry(
l,
testDomainBlocked,
testAnswerIPv4,
testClientIPv4,
filtering.FilteredBlockList,
)
addTestEntry(
l,
testDomainRewritten,
testAnswerIPv4,
testClientIPv4,
filtering.Rewritten,
)
return l
}
// testSearchAPI is a helper that makes sure that l handles GET
// /control/querylog HTTP API requests correctly.
func testSearchAPI(
tb testing.TB,
l *queryLog,
wantStatus int,
wantHosts []string,
wantMsg string,
u string,
) {
tb.Helper()
ctx := testutil.ContextWithTimeout(tb, testTimeout)
req := httptest.NewRequestWithContext(ctx, http.MethodGet, u, nil)
w := httptest.NewRecorder()
l.handleQueryLog(w, req)
assert.Equal(tb, wantStatus, w.Code)
if wantStatus != http.StatusOK {
msg, err := io.ReadAll(w.Body)
require.NoError(tb, err)
assert.Equal(tb, wantMsg, string(msg))
return
}
gotHosts := parseHostnamesFromEntry(tb, w.Body)
assert.Equal(tb, wantHosts, gotHosts)
}