package handler import ( "context" "errors" "net/http" "net/http/httptest" "strings" "testing" "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) type passkeySwitchSettingRepo struct { value string err error } func (r *passkeySwitchSettingRepo) Get(context.Context, string) (*service.Setting, error) { return nil, service.ErrSettingNotFound } func (r *passkeySwitchSettingRepo) GetValue(context.Context, string) (string, error) { return r.value, r.err } func (r *passkeySwitchSettingRepo) Set(context.Context, string, string) error { return nil } func (r *passkeySwitchSettingRepo) GetMultiple(context.Context, []string) (map[string]string, error) { return map[string]string{}, nil } func (r *passkeySwitchSettingRepo) SetMultiple(context.Context, map[string]string) error { return nil } func (r *passkeySwitchSettingRepo) GetAll(context.Context) (map[string]string, error) { return map[string]string{}, nil } func (r *passkeySwitchSettingRepo) Delete(context.Context, string) error { return nil } func TestBindPasskeyFinishRequestRejectsOversizedBody(t *testing.T) { gin.SetMode(gin.TestMode) recorder := httptest.NewRecorder() context, _ := gin.CreateTestContext(recorder) context.Request = httptest.NewRequest( http.MethodPost, "/api/v1/auth/passkey/login/finish", strings.NewReader(`{"credential":"`+strings.Repeat("x", passkeyFinishBodyMaxBytes)+`"}`), ) context.Request.Header.Set("Content-Type", "application/json") _, ok := bindPasskeyFinishRequest(context) require.False(t, ok) require.Equal(t, http.StatusBadRequest, recorder.Code) } func TestPasskeyBeginLoginRejectsDisabledAdminSwitch(t *testing.T) { gin.SetMode(gin.TestMode) repo := &passkeySwitchSettingRepo{value: "false"} settings := service.NewSettingService(repo, &config.Config{ WebAuthn: config.WebAuthnConfig{Enabled: true}, }) handler := NewPasskeyHandler(nil, nil, settings) recorder := httptest.NewRecorder() ginContext, _ := gin.CreateTestContext(recorder) ginContext.Request = httptest.NewRequest(http.MethodPost, "/api/v1/auth/passkey/login/begin", nil) handler.BeginLogin(ginContext) require.Equal(t, http.StatusForbidden, recorder.Code) require.Contains(t, recorder.Body.String(), "PASSKEY_DISABLED") } func TestPasskeyBeginLoginReportsSettingStoreFailure(t *testing.T) { gin.SetMode(gin.TestMode) settings := service.NewSettingService( &passkeySwitchSettingRepo{err: errors.New("database unavailable")}, &config.Config{WebAuthn: config.WebAuthnConfig{Enabled: true}}, ) handler := NewPasskeyHandler(nil, nil, settings) recorder := httptest.NewRecorder() ginContext, _ := gin.CreateTestContext(recorder) ginContext.Request = httptest.NewRequest(http.MethodPost, "/api/v1/auth/passkey/login/begin", nil) handler.BeginLogin(ginContext) require.Equal(t, http.StatusInternalServerError, recorder.Code) require.NotContains(t, recorder.Body.String(), "PASSKEY_DISABLED") } func TestPasskeyCredentialListRemainsAvailableWhenSignInDisabled(t *testing.T) { gin.SetMode(gin.TestMode) handler := NewPasskeyHandler(nil, nil, nil) recorder := httptest.NewRecorder() ginContext, _ := gin.CreateTestContext(recorder) ginContext.Request = httptest.NewRequest(http.MethodGet, "/api/v1/user/passkeys", nil) handler.List(ginContext) require.Equal(t, http.StatusUnauthorized, recorder.Code) require.NotContains(t, recorder.Body.String(), "PASSKEY_DISABLED") }