package api

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/go-chi/chi/v5"
	"github.com/smallstep/certificates/authority"
	"github.com/smallstep/certificates/authority/admin"
	"github.com/stretchr/testify/assert"
	"go.step.sm/linkedca"
	"google.golang.org/protobuf/encoding/protojson"
)

// ignore secret and id since those are set by the server
func assertEqualWebhook(t *testing.T, a, b *linkedca.Webhook) {
	assert.Equal(t, a.Name, b.Name)
	assert.Equal(t, a.Url, b.Url)
	assert.Equal(t, a.Kind, b.Kind)
	assert.Equal(t, a.CertType, b.CertType)
	assert.Equal(t, a.DisableTlsClientAuth, b.DisableTlsClientAuth)

	assert.Equal(t, a.GetAuth(), b.GetAuth())
}

func TestWebhookAdminResponder_CreateProvisionerWebhook(t *testing.T) {
	type test struct {
		auth       adminAuthority
		body       []byte
		ctx        context.Context
		err        *admin.Error
		response   *linkedca.Webhook
		statusCode int
	}
	var tests = map[string]func(t *testing.T) test{
		"fail/existing-webhook": func(t *testing.T) test {
			webhook := &linkedca.Webhook{
				Name: "already-exists",
				Url:  "https://example.com",
			}
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{webhook},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			err := admin.NewError(admin.ErrorConflictType, `provisioner "provName" already has a webhook with the name "already-exists"`)
			err.Message = `provisioner "provName" already has a webhook with the name "already-exists"`
			body := []byte(`
			{
				"name": "already-exists",
				"url": "https://example.com",
				"kind": "ENRICHING"
			}`)
			return test{
				ctx:        ctx,
				body:       body,
				err:        err,
				statusCode: 409,
			}
		},
		"fail/read.ProtoJSON": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "proto: syntax error (line 1:2): invalid value ?")
			adminErr.Message = "proto: syntax error (line 1:2): invalid value ?"
			body := []byte("{?}")
			return test{
				ctx:        ctx,
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/missing-name": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook name is required")
			adminErr.Message = "webhook name is required"
			body := []byte(`{"url": "https://example.com", "kind": "ENRICHING"}`)
			return test{
				ctx:        ctx,
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/missing-url": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid")
			adminErr.Message = "webhook url is invalid"
			body := []byte(`{"name": "metadata", "kind": "ENRICHING"}`)
			return test{
				ctx:        ctx,
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/relative-url": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid")
			adminErr.Message = "webhook url is invalid"
			body := []byte(`{"name": "metadata", "url": "example.com/path", "kind": "ENRICHING"}`)
			return test{
				ctx:        ctx,
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/http-url": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url must use https")
			adminErr.Message = "webhook url must use https"
			body := []byte(`{"name": "metadata", "url": "http://example.com", "kind": "ENRICHING"}`)
			return test{
				ctx:        ctx,
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/basic-auth-in-url": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url may not contain username or password")
			adminErr.Message = "webhook url may not contain username or password"
			body := []byte(`
			{
				"name": "metadata",
				"url": "https://user:pass@example.com",
				"kind": "ENRICHING"
			}`)
			return test{
				ctx:        ctx,
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/secret-in-request": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook secret must not be set")
			adminErr.Message = "webhook secret must not be set"
			body := []byte(`
			{
				"name": "metadata",
				"url": "https://example.com",
				"kind": "ENRICHING",
				"secret": "secret"
			}`)
			return test{
				ctx:        ctx,
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/unsupported-webhook-kind": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, `(line 5:13): invalid value for enum type: "UNSUPPORTED"`)
			adminErr.Message = `(line 5:13): invalid value for enum type: "UNSUPPORTED"`
			body := []byte(`
			{
				"name": "metadata",
				"url": "https://example.com",
				"kind": "UNSUPPORTED",
			}`)
			return test{
				ctx:        ctx,
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/auth.UpdateProvisioner-error": func(t *testing.T) test {
			adm := &linkedca.Admin{
				Subject: "step",
			}
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithAdmin(context.Background(), adm)
			ctx = linkedca.NewContextWithProvisioner(ctx, prov)
			adminErr := admin.NewError(admin.ErrorServerInternalType, "error creating provisioner webhook: force")
			adminErr.Message = "error creating provisioner webhook: force"
			body := []byte(`{"name": "metadata", "url": "https://example.com", "kind": "ENRICHING"}`)
			return test{
				ctx: ctx,
				auth: &mockAdminAuthority{
					MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
						return &authority.PolicyError{
							Typ: authority.StoreFailure,
							Err: errors.New("force"),
						}
					},
				},
				body:       body,
				err:        adminErr,
				statusCode: 500,
			}
		},
		"ok": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name: "provName",
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			body := []byte(`{"name": "metadata", "url": "https://example.com", "kind": "ENRICHING", "certType": "X509"}`)
			return test{
				ctx: ctx,
				auth: &mockAdminAuthority{
					MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
						assert.Equal(t, linkedca.Webhook_X509, nu.Webhooks[0].CertType)
						return nil
					},
				},
				body: body,
				response: &linkedca.Webhook{
					Name:     "metadata",
					Url:      "https://example.com",
					Kind:     linkedca.Webhook_ENRICHING,
					CertType: linkedca.Webhook_X509,
				},
				statusCode: 201,
			}
		},
	}
	for name, prep := range tests {
		tc := prep(t)
		t.Run(name, func(t *testing.T) {
			mockMustAuthority(t, tc.auth)
			ctx := admin.NewContext(tc.ctx, &admin.MockDB{})
			war := NewWebhookAdminResponder()

			req := httptest.NewRequest("POST", "/foo", io.NopCloser(bytes.NewBuffer(tc.body)))
			req = req.WithContext(ctx)
			w := httptest.NewRecorder()

			war.CreateProvisionerWebhook(w, req)
			res := w.Result()

			assert.Equal(t, tc.statusCode, res.StatusCode)

			if res.StatusCode >= 400 {

				body, err := io.ReadAll(res.Body)
				res.Body.Close()
				assert.NoError(t, err)

				ae := testAdminError{}
				assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &ae))

				assert.Equal(t, tc.err.Type, ae.Type)
				assert.Equal(t, tc.err.StatusCode(), res.StatusCode)
				assert.Equal(t, tc.err.Detail, ae.Detail)
				assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"])

				// when the error message starts with "proto", we expect it to have
				// a syntax error (in the tests). If the message doesn't start with "proto",
				// we expect a full string match.
				if strings.HasPrefix(tc.err.Message, "proto:") {
					assert.True(t, strings.Contains(ae.Message, "syntax error"))
				} else {
					assert.Equal(t, tc.err.Message, ae.Message)
				}

				return
			}

			resp := &linkedca.Webhook{}
			body, err := io.ReadAll(res.Body)
			assert.NoError(t, err)
			assert.NoError(t, protojson.Unmarshal(body, resp))

			assertEqualWebhook(t, tc.response, resp)
			assert.NotEmpty(t, resp.Secret)
			assert.NotEmpty(t, resp.Id)
		})
	}
}

func TestWebhookAdminResponder_DeleteProvisionerWebhook(t *testing.T) {
	type test struct {
		auth                adminAuthority
		err                 *admin.Error
		statusCode          int
		provisionerWebhooks []*linkedca.Webhook
		webhookName         string
	}
	var tests = map[string]func(t *testing.T) test{
		"fail/auth.UpdateProvisioner-error": func(t *testing.T) test {
			adminErr := admin.NewError(admin.ErrorServerInternalType, "error deleting provisioner webhook: force")
			adminErr.Message = "error deleting provisioner webhook: force"
			return test{
				err: adminErr,
				auth: &mockAdminAuthority{
					MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
						return &authority.PolicyError{
							Typ: authority.StoreFailure,
							Err: errors.New("force"),
						}
					},
				},
				statusCode:  500,
				webhookName: "my-webhook",
				provisionerWebhooks: []*linkedca.Webhook{
					{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING},
				},
			}
		},
		"ok/not-found": func(t *testing.T) test {
			return test{
				statusCode:          200,
				webhookName:         "no-exists",
				provisionerWebhooks: nil,
			}
		},
		"ok": func(t *testing.T) test {
			return test{
				statusCode:  200,
				webhookName: "exists",
				auth: &mockAdminAuthority{
					MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
						assert.Equal(t, nu.Webhooks, []*linkedca.Webhook{
							{Name: "my-2nd-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING},
						})
						return nil
					},
				},
				provisionerWebhooks: []*linkedca.Webhook{
					{Name: "exists", Url: "https.example.com", Kind: linkedca.Webhook_ENRICHING},
					{Name: "my-2nd-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING},
				},
			}
		},
	}
	for name, prep := range tests {
		tc := prep(t)
		t.Run(name, func(t *testing.T) {
			mockMustAuthority(t, tc.auth)

			chiCtx := chi.NewRouteContext()
			chiCtx.URLParams.Add("webhookName", tc.webhookName)
			ctx := context.WithValue(context.Background(), chi.RouteCtxKey, chiCtx)
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: tc.provisionerWebhooks,
			}
			ctx = linkedca.NewContextWithProvisioner(ctx, prov)
			ctx = admin.NewContext(ctx, &admin.MockDB{})
			req := httptest.NewRequest("DELETE", "/foo", http.NoBody).WithContext(ctx)

			war := NewWebhookAdminResponder()

			w := httptest.NewRecorder()

			war.DeleteProvisionerWebhook(w, req)
			res := w.Result()

			assert.Equal(t, tc.statusCode, res.StatusCode)

			if res.StatusCode >= 400 {

				body, err := io.ReadAll(res.Body)
				res.Body.Close()
				assert.NoError(t, err)

				ae := testAdminError{}
				assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &ae))

				assert.Equal(t, tc.err.Type, ae.Type)
				assert.Equal(t, tc.err.StatusCode(), res.StatusCode)
				assert.Equal(t, tc.err.Detail, ae.Detail)
				assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"])

				// when the error message starts with "proto", we expect it to have
				// a syntax error (in the tests). If the message doesn't start with "proto",
				// we expect a full string match.
				if strings.HasPrefix(tc.err.Message, "proto:") {
					assert.True(t, strings.Contains(ae.Message, "syntax error"))
				} else {
					assert.Equal(t, tc.err.Message, ae.Message)
				}

				return
			}

			body, err := io.ReadAll(res.Body)
			assert.NoError(t, err)
			res.Body.Close()
			response := DeleteResponse{}
			assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &response))
			assert.Equal(t, "ok", response.Status)
			assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"])
		})
	}
}

func TestWebhookAdminResponder_UpdateProvisionerWebhook(t *testing.T) {
	type test struct {
		auth       adminAuthority
		adminDB    admin.DB
		body       []byte
		ctx        context.Context
		err        *admin.Error
		response   *linkedca.Webhook
		statusCode int
	}
	var tests = map[string]func(t *testing.T) test{
		"fail/not-found": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "exists", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			err := admin.NewError(admin.ErrorNotFoundType, `provisioner "provName" has no webhook with the name "no-exists"`)
			err.Message = `provisioner "provName" has no webhook with the name "no-exists"`
			body := []byte(`
			{
				"name": "no-exists",
				"url": "https://example.com",
				"kind": "ENRICHING"
			}`)
			return test{
				ctx:        ctx,
				adminDB:    &admin.MockDB{},
				body:       body,
				err:        err,
				statusCode: 404,
			}
		},
		"fail/read.ProtoJSON": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "proto: syntax error (line 1:2): invalid value ?")
			adminErr.Message = "proto: syntax error (line 1:2): invalid value ?"
			body := []byte("{?}")
			return test{
				ctx:        ctx,
				adminDB:    &admin.MockDB{},
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/missing-name": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook name is required")
			adminErr.Message = "webhook name is required"
			body := []byte(`{"url": "https://example.com", "kind": "ENRICHING"}`)
			return test{
				ctx:        ctx,
				adminDB:    &admin.MockDB{},
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/missing-url": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid")
			adminErr.Message = "webhook url is invalid"
			body := []byte(`{"name": "metadata", "kind": "ENRICHING"}`)
			return test{
				ctx:        ctx,
				adminDB:    &admin.MockDB{},
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/relative-url": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url is invalid")
			adminErr.Message = "webhook url is invalid"
			body := []byte(`{"name": "metadata", "url": "example.com/path", "kind": "ENRICHING"}`)
			return test{
				ctx:        ctx,
				adminDB:    &admin.MockDB{},
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/http-url": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url must use https")
			adminErr.Message = "webhook url must use https"
			body := []byte(`{"name": "metadata", "url": "http://example.com", "kind": "ENRICHING"}`)
			return test{
				ctx:        ctx,
				adminDB:    &admin.MockDB{},
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/basic-auth-in-url": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook url may not contain username or password")
			adminErr.Message = "webhook url may not contain username or password"
			body := []byte(`
			{
				"name": "my-webhook",
				"url": "https://user:pass@example.com",
				"kind": "ENRICHING"
			}`)
			return test{
				ctx:        ctx,
				adminDB:    &admin.MockDB{},
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/different-secret-in-request": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING, Secret: "c2VjcmV0"}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorBadRequestType, "webhook secret cannot be updated")
			adminErr.Message = "webhook secret cannot be updated"
			body := []byte(`
			{
				"name": "my-webhook",
				"url": "https://example.com",
				"kind": "ENRICHING",
				"secret": "secret"
			}`)
			return test{
				ctx:        ctx,
				body:       body,
				err:        adminErr,
				statusCode: 400,
			}
		},
		"fail/auth.UpdateProvisioner-error": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			adminErr := admin.NewError(admin.ErrorServerInternalType, "error updating provisioner webhook: force")
			adminErr.Message = "error updating provisioner webhook: force"
			body := []byte(`{"name": "my-webhook", "url": "https://example.com", "kind": "ENRICHING"}`)
			return test{
				ctx:     ctx,
				adminDB: &admin.MockDB{},
				auth: &mockAdminAuthority{
					MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
						return &authority.PolicyError{
							Typ: authority.StoreFailure,
							Err: errors.New("force"),
						}
					},
				},
				body:       body,
				err:        adminErr,
				statusCode: 500,
			}
		},
		"ok": func(t *testing.T) test {
			prov := &linkedca.Provisioner{
				Name:     "provName",
				Webhooks: []*linkedca.Webhook{{Name: "my-webhook", Url: "https://example.com", Kind: linkedca.Webhook_ENRICHING}},
			}
			ctx := linkedca.NewContextWithProvisioner(context.Background(), prov)
			body := []byte(`{"name": "my-webhook", "url": "https://example.com", "kind": "ENRICHING"}`)
			return test{
				ctx:     ctx,
				adminDB: &admin.MockDB{},
				auth: &mockAdminAuthority{
					MockUpdateProvisioner: func(ctx context.Context, nu *linkedca.Provisioner) error {
						return nil
					},
				},
				body: body,
				response: &linkedca.Webhook{
					Name: "my-webhook",
					Url:  "https://example.com",
					Kind: linkedca.Webhook_ENRICHING,
				},
				statusCode: 201,
			}
		},
	}
	for name, prep := range tests {
		tc := prep(t)
		t.Run(name, func(t *testing.T) {
			mockMustAuthority(t, tc.auth)
			ctx := admin.NewContext(tc.ctx, tc.adminDB)
			war := NewWebhookAdminResponder()

			req := httptest.NewRequest("PUT", "/foo", io.NopCloser(bytes.NewBuffer(tc.body)))
			req = req.WithContext(ctx)
			w := httptest.NewRecorder()

			war.UpdateProvisionerWebhook(w, req)
			res := w.Result()

			assert.Equal(t, tc.statusCode, res.StatusCode)

			if res.StatusCode >= 400 {

				body, err := io.ReadAll(res.Body)
				res.Body.Close()
				assert.NoError(t, err)

				ae := testAdminError{}
				assert.NoError(t, json.Unmarshal(bytes.TrimSpace(body), &ae))

				assert.Equal(t, tc.err.Type, ae.Type)
				assert.Equal(t, tc.err.StatusCode(), res.StatusCode)
				assert.Equal(t, tc.err.Detail, ae.Detail)
				assert.Equal(t, []string{"application/json"}, res.Header["Content-Type"])

				// when the error message starts with "proto", we expect it to have
				// a syntax error (in the tests). If the message doesn't start with "proto",
				// we expect a full string match.
				if strings.HasPrefix(tc.err.Message, "proto:") {
					assert.True(t, strings.Contains(ae.Message, "syntax error"))
				} else {
					assert.Equal(t, tc.err.Message, ae.Message)
				}

				return
			}

			resp := &linkedca.Webhook{}
			body, err := io.ReadAll(res.Body)
			assert.NoError(t, err)
			assert.NoError(t, protojson.Unmarshal(body, resp))

			assertEqualWebhook(t, tc.response, resp)
		})
	}
}
