package handlers

import (
	"fmt"
	"net/mail"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/valyala/fasthttp"
	"go.uber.org/mock/gomock"

	"github.com/authelia/authelia/v4/internal/authentication"
	"github.com/authelia/authelia/v4/internal/mocks"
	"github.com/authelia/authelia/v4/internal/model"
	"github.com/authelia/authelia/v4/internal/storage"
)

func TestGetWebAuthnCredentialIDFromContext(t *testing.T) {
	testCases := []struct {
		name     string
		have     any
		expected int
		err      string
	}{
		{
			"ShouldGetCredentialID",
			"5",
			5,
			"",
		},
		{
			"ShouldNotParseInt",
			5,
			0,
			"error occurred retrieving WebAuthn Credential ID from context: the type 'int' is not a string",
		},
		{
			"ShouldNotParseAlpha",
			"abc",
			0,
			"error occurred retrieving WebAuthn Credential ID from context: failed to parse 'abc' as an integer: strconv.Atoi: parsing \"abc\": invalid syntax",
		},
		{
			"ShouldHandleMissingCredentialID",
			nil,
			0,
			"error occurred retrieving WebAuthn Credential ID from context: the user value wasn't set",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			mock := mocks.NewMockAutheliaCtx(t)

			defer mock.Close()

			if tc.have != nil {
				mock.Ctx.SetUserValue("credentialID", tc.have)
			}

			actual, theErr := getWebAuthnCredentialIDFromContext(mock.Ctx)

			if tc.err == "" {
				assert.NoError(t, theErr)
				assert.Equal(t, tc.expected, actual)
			} else {
				assert.Equal(t, 0, actual)
				assert.EqualError(t, theErr, tc.err)
			}
		})
	}
}

func TestWebAuthnCredentialsGET(t *testing.T) {
	testCases := []struct {
		name           string
		setup          func(t *testing.T, mock *mocks.MockAutheliaCtx)
		expected       string
		expectedStatus int
		expectedf      func(t *testing.T, mock *mocks.MockAutheliaCtx)
	}{
		{
			"ShouldHandleNoCredentials",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				mock.StorageMock.EXPECT().LoadWebAuthnCredentialsByUsername(mock.Ctx, exampleDotCom, testUsername).Return(nil, storage.ErrNoWebAuthnCredential)
			},
			`{"status":"OK","data":null}`,
			fasthttp.StatusOK,
			nil,
		},
		{
			"ShouldHandleAnonymous",
			nil,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusOK,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred loading WebAuthn credentials", "user is anonymous")
			},
		},
		{
			"ShouldHandleBadOrigin",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				mock.Ctx.Request.Header.Set("X-Original-URL", "haoiu123!J@#*()!@HJ$!@*(OJOIFQJNW()D@JE()_@JK")
			},
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusOK,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred loading WebAuthn credentials for user 'john': error occurred attempting to retrieve origin", "failed to parse X-Original-URL header: parse \"haoiu123!J@#*()!@HJ$!@*(OJOIFQJNW()D@JE()_@JK\": invalid URI for request")
			},
		},
		{
			"ShouldHandleStorageError",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				mock.StorageMock.EXPECT().LoadWebAuthnCredentialsByUsername(mock.Ctx, exampleDotCom, testUsername).Return(nil, fmt.Errorf("bad block"))
			},
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusOK,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred loading WebAuthn credentials for user 'john': error occurred loading credentials from the storage backend", "bad block")
			},
		},
		{
			"ShouldHandleCredentials",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				mock.StorageMock.EXPECT().LoadWebAuthnCredentialsByUsername(mock.Ctx, exampleDotCom, testUsername).Return([]model.WebAuthnCredential{{ID: 1}}, nil)
			},
			"{\"status\":\"OK\",\"data\":[{\"id\":1,\"created_at\":\"0001-01-01T00:00:00Z\",\"rpid\":\"\",\"username\":\"\",\"description\":\"\",\"kid\":\"\",\"attestation_type\":\"\",\"attachment\":\"\",\"transports\":null,\"sign_count\":0,\"clone_warning\":false,\"legacy\":false,\"discoverable\":false,\"present\":false,\"verified\":false,\"backup_eligible\":false,\"backup_state\":false,\"public_key\":\"\"}]}",
			fasthttp.StatusOK,
			nil,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			mock := mocks.NewMockAutheliaCtx(t)

			defer mock.Close()

			if tc.setup != nil {
				tc.setup(t, mock)
			}

			WebAuthnCredentialsGET(mock.Ctx)

			assert.Equal(t, tc.expectedStatus, mock.Ctx.Response.StatusCode())
			assert.Equal(t, tc.expected, string(mock.Ctx.Response.Body()))

			if tc.expectedf != nil {
				tc.expectedf(t, mock)
			}
		})
	}
}

func TestWebAuthnCredentialsPUT(t *testing.T) {
	testCases := []struct {
		name           string
		setup          func(t *testing.T, mock *mocks.MockAutheliaCtx)
		have           string
		expected       string
		expectedStatus int
		expectedf      func(t *testing.T, mock *mocks.MockAutheliaCtx)
	}{
		{
			"ShouldHandleSuccessfulAdjustment",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername}, nil),
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialsByUsername(mock.Ctx, exampleDotCom, testUsername).
						Return([]model.WebAuthnCredential{{ID: 1, Username: testUsername}}, nil),
					mock.StorageMock.
						EXPECT().
						UpdateWebAuthnCredentialDescription(mock.Ctx, testUsername, 1, "abc").
						Return(nil),
				)
			},
			`{"description":"abc"}`,
			`{"status":"OK"}`,
			fasthttp.StatusOK,
			nil,
		},
		{
			"ShouldHandleSuccessfulAdjustmentWithUnknownFields",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername}, nil),
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialsByUsername(mock.Ctx, exampleDotCom, testUsername).
						Return([]model.WebAuthnCredential{{ID: 1, Username: testUsername}}, nil),
					mock.StorageMock.
						EXPECT().
						UpdateWebAuthnCredentialDescription(mock.Ctx, testUsername, 1, "abc").
						Return(nil),
				)
			},
			`{"description":"abc","not_a_field":true}`,
			`{"status":"OK"}`,
			fasthttp.StatusOK,
			nil,
		},
		{
			"ShouldHandleDuplicateNames",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername}, nil),
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialsByUsername(mock.Ctx, exampleDotCom, testUsername).
						Return([]model.WebAuthnCredential{{ID: 1, Username: testUsername}, {ID: 2, Description: "abc", Username: testUsername}}, nil),
				)
			},
			`{"description":"abc"}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusConflict,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential for user 'john': error occurred ensuring the credentials had unique descriptions", "credential with id '2' also has the description 'abc'")
			},
		},
		{
			"ShouldHandleDuplicateFail",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername}, nil),
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialsByUsername(mock.Ctx, exampleDotCom, testUsername).
						Return(nil, fmt.Errorf("oops")),
				)
			},
			`{"description":"abc"}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential for user 'john': error occurred looking up existing credentials", "oops")
			},
		},
		{
			"ShouldHandleBadOrigin",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername}, nil),
				)

				mock.Ctx.Request.Header.Set("X-Original-URL", "##!@#!@")
			},
			`{"description":"abc"}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential for user 'john': error occurred determining the origin for the request", "failed to parse X-Original-URL header: parse \"##!@#!@\": invalid URI for request")
			},
		},
		{
			"ShouldHandleAnotherUser",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: "anotheruser"}, nil),
				)
			},
			`{"description":"abc"}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential for user 'john'", "user 'anotheruser' owns the credential with id '1'")
			},
		},
		{
			"ShouldHandleFailUpdate",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername}, nil),
					mock.StorageMock.
						EXPECT().
						LoadWebAuthnCredentialsByUsername(mock.Ctx, exampleDotCom, testUsername).
						Return([]model.WebAuthnCredential{{ID: 1, Username: testUsername}}, nil),
					mock.StorageMock.
						EXPECT().
						UpdateWebAuthnCredentialDescription(mock.Ctx, testUsername, 1, "abc").
						Return(fmt.Errorf("gremlin")),
				)
			},
			`{"description":"abc"}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential for user 'john': error occurred while attempting to update the modified credential in the storage backend", "gremlin")
			},
		},
		{
			"ShouldHandleFailLoad",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				mock.StorageMock.EXPECT().LoadWebAuthnCredentialByID(mock.Ctx, 1).Return(nil, fmt.Errorf("deleted"))
			},
			`{"description":"abc"}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential for user 'john': error occurred loading the credential from the storage backend", "deleted")
			},
		},
		{
			"ShouldHandleBadJSON",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))
			},
			`{"description:"abc"}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusBadRequest,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential for user 'john': error parsing the request body", "invalid character 'a' after object key")
			},
		},
		{
			"ShouldHandleBadDescription",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))
			},
			`{"description":""}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential for user 'john", "description is empty")
			},
		},
		{
			"ShouldHandleAnonymous",
			nil,
			`{"description":"abc"}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential", "user is anonymous")
			},
		},
		{
			"ShouldHandleBadID",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				mock.Ctx.SetUserValue("credentialID", "a")
			},
			`{"description":"abc"}`,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusBadRequest,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential for user 'john': error occurred trying to determine the credential ID", "error occurred retrieving WebAuthn Credential ID from context: failed to parse 'a' as an integer: strconv.Atoi: parsing \"a\": invalid syntax")
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			mock := mocks.NewMockAutheliaCtx(t)

			defer mock.Close()

			mock.Ctx.SetUserValue("credentialID", "1")
			mock.Ctx.Request.SetBodyString(tc.have)

			if tc.setup != nil {
				tc.setup(t, mock)
			}

			WebAuthnCredentialPUT(mock.Ctx)

			assert.Equal(t, tc.expectedStatus, mock.Ctx.Response.StatusCode())
			assert.Equal(t, tc.expected, string(mock.Ctx.Response.Body()))

			if tc.expectedf != nil {
				tc.expectedf(t, mock)
			}
		})
	}
}

func TestWebAuthnCredentialsDELETE(t *testing.T) {
	testCases := []struct {
		name           string
		setup          func(t *testing.T, mock *mocks.MockAutheliaCtx)
		expected       string
		expectedStatus int
		expectedf      func(t *testing.T, mock *mocks.MockAutheliaCtx)
	}{
		{
			"ShouldHandleSuccessfulDelete",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername, KID: model.NewBase64([]byte("abc"))}, nil),
					mock.StorageMock.EXPECT().
						DeleteWebAuthnCredential(mock.Ctx, model.NewBase64([]byte("abc")).String()).
						Return(nil),
					mock.UserProviderMock.EXPECT().
						GetDetails(testUsername).
						Return(&authentication.UserDetails{Username: testUsername, DisplayName: testDisplayName, Emails: []string{"john@example.com"}}, nil),
					mock.NotifierMock.EXPECT().
						Send(mock.Ctx, mail.Address{Name: testDisplayName, Address: "john@example.com"}, "Second Factor Method Removed", gomock.Any(), gomock.Any()).
						Return(nil),
				)
			},
			`{"status":"OK"}`,
			fasthttp.StatusOK,
			nil,
		},
		{
			"ShouldHandleSuccessfulDeleteWithNotifierError",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername, KID: model.NewBase64([]byte("abc"))}, nil),
					mock.StorageMock.EXPECT().
						DeleteWebAuthnCredential(mock.Ctx, model.NewBase64([]byte("abc")).String()).
						Return(nil),
					mock.UserProviderMock.EXPECT().
						GetDetails(testUsername).
						Return(&authentication.UserDetails{Username: testUsername, DisplayName: testDisplayName, Emails: []string{"john@example.com"}}, nil),
					mock.NotifierMock.EXPECT().
						Send(mock.Ctx, mail.Address{Name: testDisplayName, Address: "john@example.com"}, "Second Factor Method Removed", gomock.Any(), gomock.Any()).
						Return(fmt.Errorf("bad conn")),
				)
			},
			`{"status":"OK"}`,
			fasthttp.StatusOK,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred sending notification to user 'john' while attempting to alert them of an important event", "bad conn")
			},
		},
		{
			"ShouldHandleSuccessfulDeleteWithUserError",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername, KID: model.NewBase64([]byte("abc"))}, nil),
					mock.StorageMock.EXPECT().
						DeleteWebAuthnCredential(mock.Ctx, model.NewBase64([]byte("abc")).String()).
						Return(nil),
					mock.UserProviderMock.EXPECT().
						GetDetails(testUsername).
						Return(nil, fmt.Errorf("bad user")),
				)
			},
			`{"status":"OK"}`,
			fasthttp.StatusOK,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred looking up user details for user 'john' while attempting to alert them of an important event", "bad user")
			},
		},
		{
			"ShouldHandleFailedDeleteStorage",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: testUsername, KID: model.NewBase64([]byte("abc"))}, nil),
					mock.StorageMock.EXPECT().
						DeleteWebAuthnCredential(mock.Ctx, model.NewBase64([]byte("abc")).String()).
						Return(fmt.Errorf("bad pipe")),
				)
			},
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred delete WebAuthn credential for user 'john': error occurred while attempting to delete the credential from the storage backend", "bad pipe")
			},
		},
		{
			"ShouldHandleFailedLoadStorage",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(nil, fmt.Errorf("bad sql password")),
				)
			},
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred deleting WebAuthn credential for user 'john': error occurred trying to load the credential from the storage backend", "bad sql password")
			},
		},
		{
			"ShouldHandleBadUser",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				gomock.InOrder(
					mock.StorageMock.EXPECT().
						LoadWebAuthnCredentialByID(mock.Ctx, 1).
						Return(&model.WebAuthnCredential{ID: 1, Username: "baduser", KID: model.NewBase64([]byte("abc"))}, nil),
				)
			},
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred deleting WebAuthn credential for user 'john'", "user 'baduser' owns the credential with id '1'")
			},
		},
		{
			"ShouldHandleBadID",
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				us, err := mock.Ctx.GetSession()

				require.NoError(t, err)

				us.Username = testUsername
				us.AuthenticationMethodRefs.UsernameAndPassword = true

				require.NoError(t, mock.Ctx.SaveSession(us))

				mock.Ctx.SetUserValue("credentialID", "a")
			},
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusBadRequest,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred deleting WebAuthn credential for user 'john': error occurred trying to determine the credential ID", "error occurred retrieving WebAuthn Credential ID from context: failed to parse 'a' as an integer: strconv.Atoi: parsing \"a\": invalid syntax")
			},
		},
		{
			"ShouldHandleAnonymous",
			nil,
			`{"status":"KO","message":"Operation failed."}`,
			fasthttp.StatusForbidden,
			func(t *testing.T, mock *mocks.MockAutheliaCtx) {
				AssertLogEntryMessageAndError(t, mock.Hook.LastEntry(), "Error occurred modifying WebAuthn credential", "user is anonymous")
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			mock := mocks.NewMockAutheliaCtx(t)

			defer mock.Close()

			mock.Ctx.SetUserValue("credentialID", "1")

			if tc.setup != nil {
				tc.setup(t, mock)
			}

			WebAuthnCredentialDELETE(mock.Ctx)

			assert.Equal(t, tc.expectedStatus, mock.Ctx.Response.StatusCode())
			assert.Equal(t, tc.expected, string(mock.Ctx.Response.Body()))

			if tc.expectedf != nil {
				tc.expectedf(t, mock)
			}
		})
	}
}
