@@ -2,12 +2,17 @@ package pause
22
33import  (
44	"context" 
5+ 	"fmt" 
6+ 	"testing" 
57
8+ 	"github.com/stretchr/testify/require" 
9+ 	"golang.org/x/exp/slices" 
610	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 
711
812	"github.com/authzed/controller-idioms/conditions" 
913	"github.com/authzed/controller-idioms/handler" 
1014	"github.com/authzed/controller-idioms/queue" 
15+ 	"github.com/authzed/controller-idioms/queue/fake" 
1116	"github.com/authzed/controller-idioms/typedctx" 
1217)
1318
@@ -18,7 +23,7 @@ type MyObject struct {
1823	// this implements the conditions interface for MyObject, but note that 
1924	// this is not supported by kube codegen at the moment (don't try to use 
2025	// this in a real controller) 
21- 	conditions.StatusWithConditions [MyObjectStatus ] `json:"-"` 
26+ 	conditions.StatusWithConditions [* MyObjectStatus ] `json:"-"` 
2227}
2328type  MyObjectStatus  struct  {
2429	ObservedGeneration           int64  `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` 
@@ -47,3 +52,170 @@ func ExampleNewPauseContextHandler() {
4752	pauseHandler .Handle (ctx )
4853	// Output: 
4954}
55+ 
56+ func  TestPauseHandler (t  * testing.T ) {
57+ 	var  nextKey  handler.Key  =  "next" 
58+ 	const  PauseLabelKey  =  "com.my-controller/controller-paused" 
59+ 	tests  :=  []struct  {
60+ 		name  string 
61+ 
62+ 		obj         * MyObject 
63+ 		patchError  error 
64+ 
65+ 		expectNext         handler.Key 
66+ 		expectEvents       []string 
67+ 		expectPatchStatus  bool 
68+ 		expectConditions   []metav1.Condition 
69+ 		expectRequeue      bool 
70+ 		expectDone         bool 
71+ 	}{
72+ 		{
73+ 			name : "pauses when label found" ,
74+ 			obj : & MyObject {
75+ 				ObjectMeta : metav1.ObjectMeta {
76+ 					Labels : map [string ]string {
77+ 						PauseLabelKey : "" ,
78+ 					},
79+ 				},
80+ 				StatusWithConditions : conditions.StatusWithConditions [* MyObjectStatus ]{
81+ 					Status : & MyObjectStatus {
82+ 						StatusConditions : conditions.StatusConditions {
83+ 							Conditions : []metav1.Condition {},
84+ 						},
85+ 					},
86+ 				},
87+ 			},
88+ 			expectPatchStatus : true ,
89+ 			expectConditions :  []metav1.Condition {NewPausedCondition (PauseLabelKey )},
90+ 			expectDone :        true ,
91+ 		},
92+ 		{
93+ 			name : "requeues on pause patch error" ,
94+ 			obj : & MyObject {
95+ 				ObjectMeta : metav1.ObjectMeta {
96+ 					Labels : map [string ]string {
97+ 						PauseLabelKey : "" ,
98+ 					},
99+ 				},
100+ 				StatusWithConditions : conditions.StatusWithConditions [* MyObjectStatus ]{
101+ 					Status : & MyObjectStatus {
102+ 						StatusConditions : conditions.StatusConditions {
103+ 							Conditions : []metav1.Condition {},
104+ 						},
105+ 					},
106+ 				},
107+ 			},
108+ 			patchError :        fmt .Errorf ("error patching" ),
109+ 			expectPatchStatus : true ,
110+ 			expectRequeue :     true ,
111+ 		},
112+ 		{
113+ 			name : "no-op when label found and status is already paused" ,
114+ 			obj : & MyObject {
115+ 				ObjectMeta : metav1.ObjectMeta {
116+ 					Labels : map [string ]string {
117+ 						PauseLabelKey : "" ,
118+ 					},
119+ 				},
120+ 				StatusWithConditions : conditions.StatusWithConditions [* MyObjectStatus ]{
121+ 					Status : & MyObjectStatus {
122+ 						StatusConditions : conditions.StatusConditions {
123+ 							Conditions : []metav1.Condition {NewPausedCondition (PauseLabelKey )},
124+ 						},
125+ 					},
126+ 				},
127+ 			},
128+ 			expectDone : true ,
129+ 		},
130+ 		{
131+ 			name : "removes condition when label is removed" ,
132+ 			obj : & MyObject {
133+ 				StatusWithConditions : conditions.StatusWithConditions [* MyObjectStatus ]{
134+ 					Status : & MyObjectStatus {
135+ 						StatusConditions : conditions.StatusConditions {
136+ 							Conditions : []metav1.Condition {NewPausedCondition (PauseLabelKey )},
137+ 						},
138+ 					},
139+ 				},
140+ 			},
141+ 			expectPatchStatus : true ,
142+ 			expectConditions :  []metav1.Condition {},
143+ 			expectNext :        nextKey ,
144+ 		},
145+ 		{
146+ 			name : "removes self-pause condition when label is removed" ,
147+ 			obj : & MyObject {
148+ 				StatusWithConditions : conditions.StatusWithConditions [* MyObjectStatus ]{
149+ 					Status : & MyObjectStatus {
150+ 						StatusConditions : conditions.StatusConditions {
151+ 							Conditions : []metav1.Condition {NewSelfPausedCondition (PauseLabelKey )},
152+ 						},
153+ 					},
154+ 				},
155+ 			},
156+ 			expectPatchStatus : true ,
157+ 			expectConditions :  []metav1.Condition {},
158+ 			expectNext :        nextKey ,
159+ 		},
160+ 		{
161+ 			name : "requeues on unpause patch error" ,
162+ 			obj : & MyObject {
163+ 				StatusWithConditions : conditions.StatusWithConditions [* MyObjectStatus ]{
164+ 					Status : & MyObjectStatus {
165+ 						StatusConditions : conditions.StatusConditions {
166+ 							Conditions : []metav1.Condition {NewPausedCondition (PauseLabelKey )},
167+ 						},
168+ 					},
169+ 				},
170+ 			},
171+ 			patchError :        fmt .Errorf ("error patching" ),
172+ 			expectPatchStatus : true ,
173+ 			expectRequeue :     true ,
174+ 		},
175+ 		{
176+ 			name :       "no-op, no pause label, no pause status" ,
177+ 			obj :        & MyObject {},
178+ 			expectNext : nextKey ,
179+ 		},
180+ 	}
181+ 	for  _ , tt  :=  range  tests  {
182+ 		t .Run (tt .name , func (t  * testing.T ) {
183+ 			ctrls  :=  & fake.FakeInterface {}
184+ 			patchCalled  :=  false 
185+ 
186+ 			patchStatus  :=  func (ctx  context.Context , patch  * MyObject ) error  {
187+ 				patchCalled  =  true 
188+ 
189+ 				if  tt .patchError  !=  nil  {
190+ 					return  tt .patchError 
191+ 				}
192+ 
193+ 				require .Truef (t , slices .EqualFunc (tt .expectConditions , patch .Status .Conditions , func (a , b  metav1.Condition ) bool  {
194+ 					return  a .Type  ==  b .Type  && 
195+ 						a .Status  ==  b .Status  && 
196+ 						a .ObservedGeneration  ==  b .ObservedGeneration  && 
197+ 						a .Message  ==  b .Message  && 
198+ 						a .Reason  ==  b .Reason 
199+ 				}), "conditions not equal:\n a: %#v\n b: %#v" , tt .expectConditions , patch .Status .Conditions )
200+ 
201+ 				return  nil 
202+ 			}
203+ 			queueOps  :=  queue .NewQueueOperationsCtx ()
204+ 			ctxMyObject  :=  typedctx.WithDefault [* MyObject ](nil )
205+ 
206+ 			ctx  :=  context .Background ()
207+ 			ctx  =  queueOps .WithValue (ctx , ctrls )
208+ 			ctx  =  ctxMyObject .WithValue (ctx , tt .obj )
209+ 			var  called  handler.Key 
210+ 
211+ 			NewPauseContextHandler (queueOps .Key , PauseLabelKey , ctxMyObject , patchStatus , handler .ContextHandlerFunc (func (ctx  context.Context ) {
212+ 				called  =  nextKey 
213+ 			})).Handle (ctx )
214+ 
215+ 			require .Equal (t , tt .expectPatchStatus , patchCalled )
216+ 			require .Equal (t , tt .expectNext , called )
217+ 			require .Equal (t , tt .expectRequeue , ctrls .RequeueAPIErrCallCount () ==  1 )
218+ 			require .Equal (t , tt .expectDone , ctrls .DoneCallCount () ==  1 )
219+ 		})
220+ 	}
221+ }
0 commit comments