Smail/server/controllers/security_reset_entry_test.go

96 lines
3.7 KiB
Go

package controllers
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"git.pcux.cn/xuans/Smail/config"
"git.pcux.cn/xuans/Smail/dto/response"
appcontext "git.pcux.cn/xuans/Smail/utils/context"
)
func TestPublicRecoveryEnabledRequiresReadyStateAndConfiguredHost(t *testing.T) {
old := config.Instance
oldManager := config.CurrentManager()
t.Cleanup(func() {
config.Instance = old
config.SetRuntimeManager(oldManager)
})
cases := []struct {
name string
cfg *config.Config
want bool
}{
{name: "disabled", cfg: &config.Config{SetupState: config.SetupStateReady, WebDomain: "mail.example.com"}, want: false},
{name: "not_ready", cfg: &config.Config{ExternalRecoveryEnabled: true, WebDomain: "mail.example.com"}, want: false},
{name: "missing_host", cfg: &config.Config{ExternalRecoveryEnabled: true, SetupState: config.SetupStateReady}, want: false},
{name: "enabled", cfg: &config.Config{ExternalRecoveryEnabled: true, SetupState: config.SetupStateReady, WebDomain: "mail.example.com"}, want: true},
{name: "ip_host", cfg: &config.Config{ExternalRecoveryEnabled: true, SetupState: config.SetupStateReady, WebDomain: "192.0.2.10:8443"}, want: true},
{name: "configured_ip", cfg: &config.Config{ExternalRecoveryEnabled: true, SetupState: config.SetupStateReady, AccessHosts: []string{"192.0.2.11"}}, want: true},
}
for _, test := range cases {
t.Run(test.name, func(t *testing.T) {
config.SetRuntimeManager(nil)
config.Instance = test.cfg
if got := PublicRecoveryEnabled(); got != test.want {
t.Fatalf("PublicRecoveryEnabled() = %v, want %v", got, test.want)
}
})
}
}
func TestRecoveryRejectsWhenDisabled(t *testing.T) {
old := config.Instance
config.SetRuntimeManager(nil)
config.Instance = &config.Config{SetupState: config.SetupStateReady, WebDomain: "mail.example.com"}
t.Cleanup(func() { config.Instance = old })
recorder := httptest.NewRecorder()
BeginPasswordRecovery(&appcontext.Context{}, recorder, httptest.NewRequest(http.MethodPost, "/api/security/recovery/begin", nil))
if recorder.Code != http.StatusOK {
t.Fatalf("unexpected response status: %d", recorder.Code)
}
var result response.Response
if err := json.Unmarshal(recorder.Body.Bytes(), &result); err != nil {
t.Fatal(err)
}
if result.ErrorNo == 0 {
t.Fatal("disabled recovery request was accepted")
}
}
func TestRecoveryBrowserContextCookieProperties(t *testing.T) {
oldDevelopment := config.DevelopmentMode
config.DevelopmentMode = true
t.Cleanup(func() { config.DevelopmentMode = oldDevelopment })
request := httptest.NewRequest(http.MethodPost, "http://example.com/api/security/recovery/begin", nil)
recorder := httptest.NewRecorder()
value, ok := recoveryBrowserContext(request, recorder, true)
if !ok || value == "" {
t.Fatal("recovery context was not created")
}
cookie := recorder.Result().Cookies()[0]
if cookie.Name != recoveryContextCookieName || !cookie.HttpOnly || cookie.SameSite != http.SameSiteStrictMode || cookie.Path != "/api/security/recovery" || cookie.Secure {
t.Fatalf("unexpected recovery context cookie: %+v", cookie)
}
request.AddCookie(cookie)
second := httptest.NewRecorder()
if got, ok := recoveryBrowserContext(request, second, false); !ok || got != value {
t.Fatal("existing recovery context was not reused")
}
if second.Header().Get("Set-Cookie") != "" {
t.Fatal("existing recovery context was unexpectedly replaced")
}
}
func TestRecoveryBrowserContextCookieIsRequiredForCompletion(t *testing.T) {
request := httptest.NewRequest(http.MethodPost, "https://example.com/api/security/recovery/complete", nil)
recorder := httptest.NewRecorder()
if value, ok := recoveryBrowserContext(request, recorder, false); ok || value != "" {
t.Fatal("missing recovery context was accepted")
}
}