Repository: kubernetes-sigs/security-profiles-operator
Pull request #402 (2021-04-15 11:22:31)

Add profile binding unit tests
#### What type of PR is this?

/kind bug

#### What this PR does / why we need it:
Adding unit test cases to the binding webhook.

This also fixes a bug where the binding error did not get returned
correctly which would cause the webhook to crash.
#### Which issue(s) this PR fixes:

<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.

Fixes #

or

None
-->
None
#### Does this PR have test?
Yes
<!--
If tests aren't applicable just write N/A.
-->

#### Special notes for your reviewer:
None
#### Does this PR introduce a user-facing change?

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->

```release-note
None
```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/402#" title="Author self-approved">saschagrunert</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/master/OWNERS)~~ [saschagrunert]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
k8s-ci-robot: @saschagrunert: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-verify | da416774b91613f104d7abc93b0167ef8a4c3028 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/402/pull-security-profiles-operator-verify/1382655577108254720) | `/test pull-security-profiles-operator-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=402). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaschagrunert). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
codecov-io: # [Codecov](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/402?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#402](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/pull/402?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (da41677) into [master](https://codecov.io/gh/kubernetes-sigs/security-profiles-operator/commit/c43de044ffcc0ed52106da901c2c9a22973cd796?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (c43de04) will **increase** coverage by `7.70%`.
> The diff coverage is `100.00%`.

> :exclamation: Current head da41677 differs from pull request most recent head 09e51b7. Consider uploading reports for the commit 09e51b7 to get more accurate results

```diff
@@            Coverage Diff             @@
##           master     #402      +/-   ##
==========================================
+ Coverage   30.37%   38.08%   +7.70%     
==========================================
  Files          20       20              
  Lines         948      948              
==========================================
+ Hits          288      361      +73     
+ Misses        650      572      -78     
- Partials       10       15       +5     
```

JAORMX: /lgtm

Patch:
diff --git a/internal/pkg/webhooks/binding/binding.go b/internal/pkg/webhooks/binding/binding.go
index ff1b2ecc2..6f3db0477 100644
--- a/internal/pkg/webhooks/binding/binding.go
+++ b/internal/pkg/webhooks/binding/binding.go
@@ -173,7 +173,7 @@ func (p *podSeccompBinder) getSeccompProfile(
 	err = util.Retry(
 		func() (retryErr error) {
 			seccompProfile, retryErr = p.GetSeccompProfile(ctx, key)
-			if err != nil {
+			if retryErr != nil {
 				return errors.Wrapf(retryErr, "getting profile")
 			}
 			if seccompProfile.Status.Status == "" {
diff --git a/internal/pkg/webhooks/binding/bindingfakes/fake_impl.go b/internal/pkg/webhooks/binding/bindingfakes/fake_impl.go
index 5bee1381b..c00ddcb6a 100644
--- a/internal/pkg/webhooks/binding/bindingfakes/fake_impl.go
+++ b/internal/pkg/webhooks/binding/bindingfakes/fake_impl.go
@@ -22,6 +22,7 @@ import (
 	"sync"
 
 	"github.com/go-logr/logr"
+	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
@@ -30,6 +31,19 @@ import (
 )
 
 type FakeImpl struct {
+	DecodePodStub        func(admission.Request) (*v1.Pod, error)
+	decodePodMutex       sync.RWMutex
+	decodePodArgsForCall []struct {
+		arg1 admission.Request
+	}
+	decodePodReturns struct {
+		result1 *v1.Pod
+		result2 error
+	}
+	decodePodReturnsOnCall map[int]struct {
+		result1 *v1.Pod
+		result2 error
+	}
 	GetSeccompProfileStub        func(context.Context, types.NamespacedName) (*v1alpha1.SeccompProfile, error)
 	getSeccompProfileMutex       sync.RWMutex
 	getSeccompProfileArgsForCall []struct {
@@ -77,10 +91,88 @@ type FakeImpl struct {
 	updateResourceReturnsOnCall map[int]struct {
 		result1 error
 	}
+	UpdateResourceStatusStub        func(context.Context, logr.Logger, client.Object, string) error
+	updateResourceStatusMutex       sync.RWMutex
+	updateResourceStatusArgsForCall []struct {
+		arg1 context.Context
+		arg2 logr.Logger
+		arg3 client.Object
+		arg4 string
+	}
+	updateResourceStatusReturns struct {
+		result1 error
+	}
+	updateResourceStatusReturnsOnCall map[int]struct {
+		result1 error
+	}
 	invocations      map[string][][]interface{}
 	invocationsMutex sync.RWMutex
 }
 
+func (fake *FakeImpl) DecodePod(arg1 admission.Request) (*v1.Pod, error) {
+	fake.decodePodMutex.Lock()
+	ret, specificReturn := fake.decodePodReturnsOnCall[len(fake.decodePodArgsForCall)]
+	fake.decodePodArgsForCall = append(fake.decodePodArgsForCall, struct {
+		arg1 admission.Request
+	}{arg1})
+	stub := fake.DecodePodStub
+	fakeReturns := fake.decodePodReturns
+	fake.recordInvocation("DecodePod", []interface{}{arg1})
+	fake.decodePodMutex.Unlock()
+	if stub != nil {
+		return stub(arg1)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeImpl) DecodePodCallCount() int {
+	fake.decodePodMutex.RLock()
+	defer fake.decodePodMutex.RUnlock()
+	return len(fake.decodePodArgsForCall)
+}
+
+func (fake *FakeImpl) DecodePodCalls(stub func(admission.Request) (*v1.Pod, error)) {
+	fake.decodePodMutex.Lock()
+	defer fake.decodePodMutex.Unlock()
+	fake.DecodePodStub = stub
+}
+
+func (fake *FakeImpl) DecodePodArgsForCall(i int) admission.Request {
+	fake.decodePodMutex.RLock()
+	defer fake.decodePodMutex.RUnlock()
+	argsForCall := fake.decodePodArgsForCall[i]
+	return argsForCall.arg1
+}
+
+func (fake *FakeImpl) DecodePodReturns(result1 *v1.Pod, result2 error) {
+	fake.decodePodMutex.Lock()
+	defer fake.decodePodMutex.Unlock()
+	fake.DecodePodStub = nil
+	fake.decodePodReturns = struct {
+		result1 *v1.Pod
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeImpl) DecodePodReturnsOnCall(i int, result1 *v1.Pod, result2 error) {
+	fake.decodePodMutex.Lock()
+	defer fake.decodePodMutex.Unlock()
+	fake.DecodePodStub = nil
+	if fake.decodePodReturnsOnCall == nil {
+		fake.decodePodReturnsOnCall = make(map[int]struct {
+			result1 *v1.Pod
+			result2 error
+		})
+	}
+	fake.decodePodReturnsOnCall[i] = struct {
+		result1 *v1.Pod
+		result2 error
+	}{result1, result2}
+}
+
 func (fake *FakeImpl) GetSeccompProfile(arg1 context.Context, arg2 types.NamespacedName) (*v1alpha1.SeccompProfile, error) {
 	fake.getSeccompProfileMutex.Lock()
 	ret, specificReturn := fake.getSeccompProfileReturnsOnCall[len(fake.getSeccompProfileArgsForCall)]
@@ -307,9 +399,75 @@ func (fake *FakeImpl) UpdateResourceReturnsOnCall(i int, result1 error) {
 	}{result1}
 }
 
+func (fake *FakeImpl) UpdateResourceStatus(arg1 context.Context, arg2 logr.Logger, arg3 client.Object, arg4 string) error {
+	fake.updateResourceStatusMutex.Lock()
+	ret, specificReturn := fake.updateResourceStatusReturnsOnCall[len(fake.updateResourceStatusArgsForCall)]
+	fake.updateResourceStatusArgsForCall = append(fake.updateResourceStatusArgsForCall, struct {
+		arg1 context.Context
+		arg2 logr.Logger
+		arg3 client.Object
+		arg4 string
+	}{arg1, arg2, arg3, arg4})
+	stub := fake.UpdateResourceStatusStub
+	fakeReturns := fake.updateResourceStatusReturns
+	fake.recordInvocation("UpdateResourceStatus", []interface{}{arg1, arg2, arg3, arg4})
+	fake.updateResourceStatusMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3, arg4)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *FakeImpl) UpdateResourceStatusCallCount() int {
+	fake.updateResourceStatusMutex.RLock()
+	defer fake.updateResourceStatusMutex.RUnlock()
+	return len(fake.updateResourceStatusArgsForCall)
+}
+
+func (fake *FakeImpl) UpdateResourceStatusCalls(stub func(context.Context, logr.Logger, client.Object, string) error) {
+	fake.updateResourceStatusMutex.Lock()
+	defer fake.updateResourceStatusMutex.Unlock()
+	fake.UpdateResourceStatusStub = stub
+}
+
+func (fake *FakeImpl) UpdateResourceStatusArgsForCall(i int) (context.Context, logr.Logger, client.Object, string) {
+	fake.updateResourceStatusMutex.RLock()
+	defer fake.updateResourceStatusMutex.RUnlock()
+	argsForCall := fake.updateResourceStatusArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
+}
+
+func (fake *FakeImpl) UpdateResourceStatusReturns(result1 error) {
+	fake.updateResourceStatusMutex.Lock()
+	defer fake.updateResourceStatusMutex.Unlock()
+	fake.UpdateResourceStatusStub = nil
+	fake.updateResourceStatusReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *FakeImpl) UpdateResourceStatusReturnsOnCall(i int, result1 error) {
+	fake.updateResourceStatusMutex.Lock()
+	defer fake.updateResourceStatusMutex.Unlock()
+	fake.UpdateResourceStatusStub = nil
+	if fake.updateResourceStatusReturnsOnCall == nil {
+		fake.updateResourceStatusReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.updateResourceStatusReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
 func (fake *FakeImpl) Invocations() map[string][][]interface{} {
 	fake.invocationsMutex.RLock()
 	defer fake.invocationsMutex.RUnlock()
+	fake.decodePodMutex.RLock()
+	defer fake.decodePodMutex.RUnlock()
 	fake.getSeccompProfileMutex.RLock()
 	defer fake.getSeccompProfileMutex.RUnlock()
 	fake.listProfileBindingsMutex.RLock()
@@ -318,6 +476,8 @@ func (fake *FakeImpl) Invocations() map[string][][]interface{} {
 	defer fake.setDecoderMutex.RUnlock()
 	fake.updateResourceMutex.RLock()
 	defer fake.updateResourceMutex.RUnlock()
+	fake.updateResourceStatusMutex.RLock()
+	defer fake.updateResourceStatusMutex.RUnlock()
 	copiedInvocations := map[string][][]interface{}{}
 	for key, value := range fake.invocations {
 		copiedInvocations[key] = value


Test patch:
diff --git a/internal/pkg/webhooks/binding/binding_test.go b/internal/pkg/webhooks/binding/binding_test.go
index 75743f06f..6e37e303a 100644
--- a/internal/pkg/webhooks/binding/binding_test.go
+++ b/internal/pkg/webhooks/binding/binding_test.go
@@ -17,13 +17,363 @@ limitations under the License.
 package binding
 
 import (
+	"context"
+	"encoding/json"
+	"net/http"
 	"sync"
 	"testing"
 
+	"github.com/go-logr/logr"
+	"github.com/pkg/errors"
 	"github.com/stretchr/testify/require"
+	admissionv1 "k8s.io/api/admission/v1"
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	profilebasev1alpha1 "sigs.k8s.io/security-profiles-operator/api/profilebase/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/api/profilebinding/v1alpha1"
+	seccompv1alpha1 "sigs.k8s.io/security-profiles-operator/api/seccompprofile/v1alpha1"
+	secprofnodestatusv1alpha1 "sigs.k8s.io/security-profiles-operator/api/secprofnodestatus/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/webhooks/binding/bindingfakes"
 )
 
+var (
+	errTest = errors.New("error")
+	testPod = &corev1.Pod{
+		ObjectMeta: metav1.ObjectMeta{
+			GenerateName: "pod-",
+		},
+		Spec: corev1.PodSpec{
+			Containers: []corev1.Container{
+				{
+					Name: "container",
+				},
+			},
+		},
+	}
+)
+
+func TestHandle(t *testing.T) {
+	t.Parallel()
+
+	for _, tc := range []struct {
+		prepare func(*bindingfakes.FakeImpl)
+		request admission.Request
+		assert  func(admission.Response)
+	}{
+		{ // success pod unchanged
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{}, nil)
+				mock.DecodePodReturns(&corev1.Pod{}, nil)
+			},
+			assert: func(resp admission.Response) {
+				require.True(t, resp.Allowed)
+				require.Equal(t, http.StatusOK, int(resp.Result.Code))
+				require.Equal(t, metav1.StatusReason("pod unchanged"), resp.Result.Reason)
+			},
+		},
+		{ // error could not list profile bindings
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(nil, errTest)
+			},
+			assert: func(resp admission.Response) {
+				require.Equal(t, http.StatusInternalServerError, int(resp.Result.Code))
+			},
+		},
+		{ // error failed to decode pod
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{}, nil)
+				mock.DecodePodReturns(nil, errTest)
+			},
+			assert: func(resp admission.Response) {
+				require.Equal(t, http.StatusBadRequest, int(resp.Result.Code))
+			},
+		},
+		{ // success pod changed
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{
+					Items: []v1alpha1.ProfileBinding{
+						{
+							Spec: v1alpha1.ProfileBindingSpec{
+								ProfileRef: v1alpha1.ProfileRef{
+									Kind: "SeccompProfile",
+								},
+							},
+						},
+					},
+				}, nil)
+				mock.DecodePodReturns(testPod.DeepCopy(), nil)
+				mock.GetSeccompProfileReturns(&seccompv1alpha1.SeccompProfile{
+					Status: seccompv1alpha1.SeccompProfileStatus{
+						StatusBase: profilebasev1alpha1.StatusBase{
+							Status: secprofnodestatusv1alpha1.ProfileStateInstalled,
+						},
+					},
+				}, nil)
+			},
+			request: admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Object: runtime.RawExtension{
+						Raw: func() []byte {
+							b, err := json.Marshal(testPod.DeepCopy())
+							require.Nil(t, err)
+							return b
+						}(),
+					},
+				},
+			},
+			assert: func(resp admission.Response) {
+				require.True(t, resp.AdmissionResponse.Allowed)
+				require.Len(t, resp.Patches, 1)
+			},
+		},
+		{ // success unsupported kind
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{
+					Items: []v1alpha1.ProfileBinding{
+						{
+							Spec: v1alpha1.ProfileBindingSpec{
+								ProfileRef: v1alpha1.ProfileRef{
+									Kind: "unsupported",
+								},
+							},
+						},
+					},
+				}, nil)
+				mock.DecodePodReturns(testPod.DeepCopy(), nil)
+				mock.GetSeccompProfileReturns(&seccompv1alpha1.SeccompProfile{
+					Status: seccompv1alpha1.SeccompProfileStatus{
+						StatusBase: profilebasev1alpha1.StatusBase{
+							Status: secprofnodestatusv1alpha1.ProfileStateInstalled,
+						},
+					},
+				}, nil)
+			},
+			request: admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Object: runtime.RawExtension{
+						Raw: func() []byte {
+							b, err := json.Marshal(testPod.DeepCopy())
+							require.Nil(t, err)
+							return b
+						}(),
+					},
+				},
+			},
+			assert: func(resp admission.Response) {
+				require.True(t, resp.AdmissionResponse.Allowed)
+				require.Empty(t, resp.Patches)
+			},
+		},
+		{ // failure get seccomp profile malicious
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{
+					Items: []v1alpha1.ProfileBinding{
+						{
+							Spec: v1alpha1.ProfileBindingSpec{
+								ProfileRef: v1alpha1.ProfileRef{
+									Kind: "SeccompProfile",
+								},
+							},
+						},
+					},
+				}, nil)
+				mock.DecodePodReturns(testPod.DeepCopy(), nil)
+				mock.GetSeccompProfileReturns(&seccompv1alpha1.SeccompProfile{
+					Status: seccompv1alpha1.SeccompProfileStatus{},
+				}, nil)
+			},
+			request: admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Object: runtime.RawExtension{
+						Raw: func() []byte {
+							b, err := json.Marshal(testPod.DeepCopy())
+							require.Nil(t, err)
+							return b
+						}(),
+					},
+				},
+			},
+			assert: func(resp admission.Response) {
+				require.Equal(t, http.StatusInternalServerError, int(resp.Result.Code))
+			},
+		},
+		{ // failure get seccomp profile errored
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{
+					Items: []v1alpha1.ProfileBinding{
+						{
+							Spec: v1alpha1.ProfileBindingSpec{
+								ProfileRef: v1alpha1.ProfileRef{
+									Kind: "SeccompProfile",
+								},
+							},
+						},
+					},
+				}, nil)
+				mock.DecodePodReturns(testPod.DeepCopy(), nil)
+				mock.GetSeccompProfileReturns(nil, errTest)
+			},
+			request: admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Object: runtime.RawExtension{
+						Raw: func() []byte {
+							b, err := json.Marshal(testPod.DeepCopy())
+							require.Nil(t, err)
+							return b
+						}(),
+					},
+				},
+			},
+			assert: func(resp admission.Response) {
+				require.Equal(t, http.StatusInternalServerError, int(resp.Result.Code))
+			},
+		},
+		// nolint: dupl
+		{ // failure on UpdateResource
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{
+					Items: []v1alpha1.ProfileBinding{
+						{
+							Spec: v1alpha1.ProfileBindingSpec{
+								ProfileRef: v1alpha1.ProfileRef{
+									Kind: "SeccompProfile",
+								},
+							},
+						},
+					},
+				}, nil)
+				mock.DecodePodReturns(testPod.DeepCopy(), nil)
+				mock.GetSeccompProfileReturns(&seccompv1alpha1.SeccompProfile{
+					Status: seccompv1alpha1.SeccompProfileStatus{
+						StatusBase: profilebasev1alpha1.StatusBase{
+							Status: secprofnodestatusv1alpha1.ProfileStateInstalled,
+						},
+					},
+				}, nil)
+				mock.UpdateResourceReturns(errTest)
+			},
+			request: admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Object: runtime.RawExtension{
+						Raw: func() []byte {
+							b, err := json.Marshal(testPod.DeepCopy())
+							require.Nil(t, err)
+							return b
+						}(),
+					},
+				},
+			},
+			assert: func(resp admission.Response) {
+				require.Equal(t, http.StatusInternalServerError, int(resp.Result.Code))
+			},
+		},
+		// nolint: dupl
+		{ // failure on UpdateResourceStatus
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{
+					Items: []v1alpha1.ProfileBinding{
+						{
+							Spec: v1alpha1.ProfileBindingSpec{
+								ProfileRef: v1alpha1.ProfileRef{
+									Kind: "SeccompProfile",
+								},
+							},
+						},
+					},
+				}, nil)
+				mock.DecodePodReturns(testPod.DeepCopy(), nil)
+				mock.GetSeccompProfileReturns(&seccompv1alpha1.SeccompProfile{
+					Status: seccompv1alpha1.SeccompProfileStatus{
+						StatusBase: profilebasev1alpha1.StatusBase{
+							Status: secprofnodestatusv1alpha1.ProfileStateInstalled,
+						},
+					},
+				}, nil)
+				mock.UpdateResourceStatusReturns(errTest)
+			},
+			request: admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Object: runtime.RawExtension{
+						Raw: func() []byte {
+							b, err := json.Marshal(testPod.DeepCopy())
+							require.Nil(t, err)
+							return b
+						}(),
+					},
+				},
+			},
+			assert: func(resp admission.Response) {
+				require.Equal(t, http.StatusInternalServerError, int(resp.Result.Code))
+			},
+		},
+		{ // success pod deleted
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{
+					Items: []v1alpha1.ProfileBinding{
+						{
+							Spec: v1alpha1.ProfileBindingSpec{
+								ProfileRef: v1alpha1.ProfileRef{
+									Kind: "SeccompProfile",
+								},
+							},
+							Status: v1alpha1.ProfileBindingStatus{
+								ActiveWorkloads: []string{"1", "2", "3"},
+							},
+						},
+					},
+				}, nil)
+				mock.DecodePodReturns(testPod.DeepCopy(), nil)
+			},
+			request: admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Operation: admissionv1.Delete,
+				},
+			},
+			assert: func(resp admission.Response) {
+				require.True(t, resp.AdmissionResponse.Allowed)
+			},
+		},
+		{ // failure delete on remove pod from binding at  UpdateResourceStatus
+			prepare: func(mock *bindingfakes.FakeImpl) {
+				mock.ListProfileBindingsReturns(&v1alpha1.ProfileBindingList{
+					Items: []v1alpha1.ProfileBinding{
+						{
+							Spec: v1alpha1.ProfileBindingSpec{
+								ProfileRef: v1alpha1.ProfileRef{
+									Kind: "SeccompProfile",
+								},
+							},
+							Status: v1alpha1.ProfileBindingStatus{
+								ActiveWorkloads: []string{"1", "2", "3"},
+							},
+						},
+					},
+				}, nil)
+				mock.DecodePodReturns(testPod.DeepCopy(), nil)
+				mock.UpdateResourceStatusReturns(errTest)
+			},
+			request: admission.Request{
+				AdmissionRequest: admissionv1.AdmissionRequest{
+					Operation: admissionv1.Delete,
+				},
+			},
+			assert: func(resp admission.Response) {
+				require.Equal(t, http.StatusInternalServerError, int(resp.Result.Code))
+			},
+		},
+	} {
+		mock := &bindingfakes.FakeImpl{}
+		tc.prepare(mock)
+
+		binder := podSeccompBinder{impl: mock, log: logr.Discard()}
+		resp := binder.Handle(context.Background(), tc.request)
+		tc.assert(resp)
+	}
+}
+
 func TestNewContainerMap(t *testing.T) {
 	t.Parallel()