lint / go (push) Canceled after 0s
lint / go_mod (push) Canceled after 0s
lint / conf (push) Canceled after 0s
lint / docslinks (push) Canceled after 0s
lint / docsorder (push) Canceled after 0s
lint / apidocs (push) Canceled after 0s
lint / other (push) Canceled after 0s
test / test_64 (push) Canceled after 0s
test / test_32 (push) Canceled after 0s
test / test_e2e (push) Canceled after 0s
166 lines
5.7 KiB
Go
166 lines
5.7 KiB
Go
package api //nolint:revive
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/bluenviron/mediamtx/internal/auth"
|
|
"github.com/bluenviron/mediamtx/internal/conf"
|
|
"github.com/bluenviron/mediamtx/internal/test"
|
|
)
|
|
|
|
const adminTestAddress = "localhost:9996"
|
|
|
|
func newAdminTestAPI(t *testing.T, authManager apiAuthManager) *API {
|
|
t.Helper()
|
|
|
|
api := &API{
|
|
Address: adminTestAddress,
|
|
ReadTimeout: conf.Duration(10 * time.Second),
|
|
WriteTimeout: conf.Duration(10 * time.Second),
|
|
AuthManager: authManager,
|
|
Parent: &testParent{},
|
|
}
|
|
require.NoError(t, api.Initialize())
|
|
t.Cleanup(api.Close)
|
|
return api
|
|
}
|
|
|
|
func readAdminResponse(t *testing.T, client *http.Client, url string) (*http.Response, string) {
|
|
t.Helper()
|
|
|
|
res, err := client.Get(url)
|
|
require.NoError(t, err)
|
|
body, err := io.ReadAll(res.Body)
|
|
require.NoError(t, err)
|
|
require.NoError(t, res.Body.Close())
|
|
return res, string(body)
|
|
}
|
|
|
|
func TestAdminAssets(t *testing.T) {
|
|
authCalls := 0
|
|
newAdminTestAPI(t, &test.AuthManager{
|
|
AuthenticateImpl: func(_ *auth.Request) (string, *auth.Error) {
|
|
authCalls++
|
|
return "", &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("denied")}
|
|
},
|
|
})
|
|
|
|
transport := &http.Transport{}
|
|
t.Cleanup(transport.CloseIdleConnections)
|
|
client := &http.Client{Transport: transport}
|
|
|
|
res, body := readAdminResponse(t, client, "http://"+adminTestAddress+"/admin/")
|
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
require.Equal(t, "text/html; charset=utf-8", res.Header.Get("Content-Type"))
|
|
require.Equal(t, "no-store", res.Header.Get("Cache-Control"))
|
|
require.Contains(t, res.Header.Get("Content-Security-Policy"), "object-src 'none'")
|
|
require.Equal(t, "nosniff", res.Header.Get("X-Content-Type-Options"))
|
|
require.Contains(t, body, "MediaMTX Stream Console")
|
|
require.Contains(t, body, `id="login-form"`)
|
|
require.Contains(t, body, `id="logout-button"`)
|
|
require.Contains(t, body, "./app.css")
|
|
require.Contains(t, body, "./list-tools.js")
|
|
require.Contains(t, body, "./app.js")
|
|
require.NotContains(t, body, "https://")
|
|
require.Zero(t, authCalls)
|
|
|
|
res, body = readAdminResponse(t, client, "http://"+adminTestAddress+"/admin/app.css")
|
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
require.Equal(t, "text/css; charset=utf-8", res.Header.Get("Content-Type"))
|
|
require.Contains(t, body, "prefers-reduced-motion")
|
|
require.Contains(t, body, "@media (max-width: 700px)")
|
|
|
|
res, body = readAdminResponse(t, client, "http://"+adminTestAddress+"/admin/app.js")
|
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
require.Equal(t, "text/javascript; charset=utf-8", res.Header.Get("Content-Type"))
|
|
require.Contains(t, body, "sourceMasked")
|
|
require.Contains(t, body, "encodePathName")
|
|
require.Contains(t, body, "if (source) payload.source = source")
|
|
require.Contains(t, body, "redactSensitiveText")
|
|
require.Contains(t, body, "state.global.webrtcAddress")
|
|
require.Contains(t, body, "sessionStorage")
|
|
require.Contains(t, body, "Authorization")
|
|
require.NotContains(t, body, ".innerHTML")
|
|
require.NotContains(t, body, "localStorage")
|
|
|
|
res, body = readAdminResponse(t, client, "http://"+adminTestAddress+"/admin/list-tools.js")
|
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
require.Equal(t, "text/javascript; charset=utf-8", res.Header.Get("Content-Type"))
|
|
require.Contains(t, body, "fetchAllPages")
|
|
require.Contains(t, body, "API 返回了无效的分页数据")
|
|
require.Contains(t, body, "concurrency")
|
|
require.NotContains(t, body, ".innerHTML")
|
|
|
|
res, _ = readAdminResponse(t, client, "http://"+adminTestAddress+"/admin/missing.js")
|
|
require.Equal(t, http.StatusNotFound, res.StatusCode)
|
|
}
|
|
|
|
func TestAdminRedirect(t *testing.T) {
|
|
newAdminTestAPI(t, test.NilAuthManager)
|
|
|
|
transport := &http.Transport{}
|
|
t.Cleanup(transport.CloseIdleConnections)
|
|
client := &http.Client{
|
|
Transport: transport,
|
|
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
|
|
res, _ := readAdminResponse(t, client, "http://"+adminTestAddress+"/admin")
|
|
require.Equal(t, http.StatusMovedPermanently, res.StatusCode)
|
|
require.Equal(t, "/admin/", res.Header.Get("Location"))
|
|
}
|
|
|
|
func TestAdminAPIUsesAuthentication(t *testing.T) {
|
|
var got *auth.Request
|
|
newAdminTestAPI(t, &test.AuthManager{
|
|
AuthenticateImpl: func(req *auth.Request) (string, *auth.Error) {
|
|
got = req
|
|
return "", &auth.Error{AskCredentials: true, Wrapped: fmt.Errorf("denied")}
|
|
},
|
|
})
|
|
|
|
transport := &http.Transport{}
|
|
t.Cleanup(transport.CloseIdleConnections)
|
|
client := &http.Client{Transport: transport}
|
|
|
|
res, body := readAdminResponse(t, client, "http://"+adminTestAddress+"/v3/info")
|
|
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
|
|
require.Equal(t, `Basic realm="mediamtx"`, res.Header.Get("WWW-Authenticate"))
|
|
require.NotNil(t, got)
|
|
require.Equal(t, conf.AuthActionAPI, got.Action)
|
|
require.True(t, got.EnableAskCredentials)
|
|
require.True(t, strings.Contains(body, "authentication error"))
|
|
}
|
|
|
|
func TestAdminAPIAcceptsBasicCredentials(t *testing.T) {
|
|
newAdminTestAPI(t, &test.AuthManager{
|
|
AuthenticateImpl: func(req *auth.Request) (string, *auth.Error) {
|
|
require.Equal(t, conf.AuthActionAPI, req.Action)
|
|
require.Equal(t, "test-admin", req.Credentials.User)
|
|
require.Equal(t, "test-password", req.Credentials.Pass)
|
|
return req.Credentials.User, nil
|
|
},
|
|
})
|
|
|
|
transport := &http.Transport{}
|
|
t.Cleanup(transport.CloseIdleConnections)
|
|
client := &http.Client{Transport: transport}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://"+adminTestAddress+"/v3/info", nil)
|
|
require.NoError(t, err)
|
|
req.SetBasicAuth("test-admin", "test-password")
|
|
res, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
require.NoError(t, res.Body.Close())
|
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
|
}
|