AdGuardHome/internal/home/profilehttp_internal_test.go
Stanislav Chzhen 8f1940f759 Pull request 2497: AGDNS-3306-rm-global-conf-filepath
Squashed commit of the following:

commit 6d50f74a25c8dcf2627bbb88674cca0ed7b2bbe0
Merge: aff0466ba 5c9fef62f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Oct 23 18:46:46 2025 +0300

    Merge branch 'master' into AGDNS-3306-rm-global-conf-filepath

commit aff0466ba7
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Oct 21 10:17:59 2025 +0300

    home: fix tests

commit 8e2e1d871f
Merge: fe67680f7 feef4cd2a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Oct 17 09:19:13 2025 +0300

    Merge branch 'master' into AGDNS-3306-rm-global-conf-filepath

commit fe67680f7b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Oct 17 09:13:37 2025 +0300

    home: add todo

commit 6dc5635a7e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Oct 15 19:43:37 2025 +0300

    home: add test cases

commit 233263bd5f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Oct 15 10:22:41 2025 +0300

    home: add test

commit 354e13a60e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Oct 10 23:07:21 2025 +0300

    home: imp logs

commit 8541861248
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Oct 8 15:36:50 2025 +0300

    home: rm global conf filepath
2025-10-23 18:59:44 +03:00

270 lines
6.4 KiB
Go

package home
import (
"bytes"
"context"
"encoding/binary"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/agh"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
)
func TestWeb_HandleGetProfile(t *testing.T) {
storeGlobals(t)
const (
testTTL = 60
glTokenFileSuffix = "test"
userName = "name"
userPassword = "password"
path = "/control/profile"
)
passwordHash, err := bcrypt.GenerateFromPassword([]byte(userPassword), bcrypt.DefaultCost)
require.NoError(t, err)
tempDir := t.TempDir()
glFilePrefix = tempDir + "/gl_token_"
glTokenFile := glFilePrefix + glTokenFileSuffix
glFileData := make([]byte, 4)
binary.NativeEndian.PutUint32(glFileData, uint32(time.Now().Unix()+testTTL))
err = os.WriteFile(glTokenFile, glFileData, 0o644)
require.NoError(t, err)
sessionsDB := filepath.Join(tempDir, "sessions.db")
user := &webUser{
Name: userName,
PasswordHash: string(passwordHash),
}
auth, err := newAuth(testutil.ContextWithTimeout(t, testTimeout), &authConfig{
baseLogger: testLogger,
rateLimiter: emptyRateLimiter{},
trustedProxies: nil,
dbFilename: sessionsDB,
users: nil,
sessionTTL: testTTL * time.Second,
isGLiNet: false,
})
require.NoError(t, err)
t.Cleanup(func() { auth.close(testutil.ContextWithTimeout(t, testTimeout)) })
baseMux := http.NewServeMux()
tlsMgr, err := newTLSManager(testutil.ContextWithTimeout(t, testTimeout), &tlsManagerConfig{
logger: testLogger,
confModifier: agh.EmptyConfigModifier{},
})
require.NoError(t, err)
web, err := initWeb(
testutil.ContextWithTimeout(t, testTimeout),
options{},
nil,
nil,
testLogger,
tlsMgr,
auth,
baseMux,
agh.EmptyConfigModifier{},
aghhttp.EmptyRegistrar{},
"",
"",
false,
false,
)
require.NoError(t, err)
globalContext.web = web
mux := auth.middleware().Wrap(baseMux)
require.True(t, t.Run("userless", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, path, nil)
web.handleGetProfile(w, r)
assert.Equal(t, http.StatusOK, w.Code)
}))
require.True(t, t.Run("add_user", func(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, testTimeout)
err = auth.addUser(ctx, user, userPassword)
require.NoError(t, err)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, path, nil)
web.handleGetProfile(w, r)
assert.Equal(t, http.StatusUnauthorized, w.Code)
w = httptest.NewRecorder()
r = httptest.NewRequest(http.MethodGet, path, nil)
loginCookie := generateAuthCookie(t, mux, userName, userPassword)
r.AddCookie(loginCookie)
web.handleGetProfile(w, r)
assert.Equal(t, http.StatusUnauthorized, w.Code)
}))
}
func TestWeb_HandlePutProfile(t *testing.T) {
storeGlobals(t)
mw := &webMw{}
mux := http.NewServeMux()
httpReg := aghhttp.NewDefaultRegistrar(mux, mw.wrap)
isConfigChanged := false
confModifier := &aghtest.ConfigModifier{
OnApply: func(_ context.Context) { isConfigChanged = true },
}
ctx := testutil.ContextWithTimeout(t, testTimeout)
web, err := initWeb(
ctx,
options{},
nil,
nil,
testLogger,
nil,
nil,
mux,
confModifier,
httpReg,
"",
"",
false,
false,
)
require.NoError(t, err)
globalContext.web = web
mw.set(web)
var (
dataValid = errors.Must(json.Marshal(&profileJSON{
Language: "en",
Theme: "auto",
}))
dataInvalidLang = errors.Must(json.Marshal(&profileJSON{
Language: "invalid_lang",
Theme: "auto",
}))
dataInvalidTheme = errors.Must(json.Marshal(&profileJSON{
Language: "en",
Theme: "invalid_theme",
}))
)
testCases := []struct {
req *http.Request
name string
wantBody string
wantCode int
}{{
req: newProfileUpdateRequest(http.MethodPut, dataValid, true),
name: "basic",
wantBody: "OK\n",
wantCode: http.StatusOK,
}, {
req: newProfileUpdateRequest(http.MethodGet, dataValid, true),
name: "invalid_method",
wantBody: "only method PUT is allowed\n",
wantCode: http.StatusMethodNotAllowed,
}, {
req: newProfileUpdateRequest(http.MethodPut, dataValid, false),
name: "invalid_content_type",
wantBody: "only content-type application/json is allowed\n",
wantCode: http.StatusUnsupportedMediaType,
}, {
req: newProfileUpdateRequest(http.MethodPut, nil, false),
name: "empty_body",
wantBody: "reading req: EOF\n",
wantCode: http.StatusBadRequest,
}, {
req: newProfileUpdateRequest(http.MethodPut, dataInvalidLang, true),
name: "invalid_language",
wantBody: `unknown language: "invalid_lang"` + "\n",
wantCode: http.StatusBadRequest,
}, {
req: newProfileUpdateRequest(http.MethodPut, dataInvalidTheme, true),
name: "invalid_theme",
wantBody: `reading req: invalid theme "invalid_theme", ` +
`supported: "auto", "dark", "light"` + "\n",
wantCode: http.StatusBadRequest,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
w := httptest.NewRecorder()
mux.ServeHTTP(w, tc.req)
assert.Equal(t, tc.wantCode, w.Code)
assert.Equal(t, tc.wantBody, w.Body.String())
})
}
require.True(t, t.Run("single_config_update", func(t *testing.T) {
isConfigChanged = false
config.Language = ""
config.Theme = ""
w := httptest.NewRecorder()
mux.ServeHTTP(w, newProfileUpdateRequest(http.MethodPut, dataValid, true))
require.Equal(t, http.StatusOK, w.Code)
assert.True(t, isConfigChanged)
isConfigChanged = false
mux.ServeHTTP(w, newProfileUpdateRequest(http.MethodPut, dataValid, true))
require.Equal(t, http.StatusOK, w.Code)
assert.False(t, isConfigChanged)
}))
}
// newProfileUpdateRequest builds an *http.Request for the profile update
// endpoint. If body is non-nil, it is used as the request body. If setCT is
// true, the Content-Type header is set to application/json.
func newProfileUpdateRequest(method string, body []byte, setCT bool) (req *http.Request) {
var r io.Reader
if body != nil {
r = bytes.NewReader(body)
}
req = httptest.NewRequest(method, "/control/profile/update", r)
if setCT {
req.Header.Set(httphdr.ContentType, aghhttp.HdrValApplicationJSON)
}
return req
}