k8s.io/kubernetes@v1.29.3/pkg/controller/serviceaccount/legacy_serviceaccount_token_cleaner_test.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package serviceaccount 18 19 import ( 20 "context" 21 "encoding/json" 22 "reflect" 23 "testing" 24 "time" 25 26 v1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 "k8s.io/apimachinery/pkg/types" 31 applyv1 "k8s.io/client-go/applyconfigurations/core/v1" 32 "k8s.io/client-go/informers" 33 "k8s.io/client-go/kubernetes/fake" 34 core "k8s.io/client-go/testing" 35 "k8s.io/kubernetes/pkg/controller" 36 "k8s.io/kubernetes/pkg/controlplane/controller/legacytokentracking" 37 "k8s.io/kubernetes/pkg/serviceaccount" 38 testingclock "k8s.io/utils/clock/testing" 39 ) 40 41 func configuredConfigMap(label string) *v1.ConfigMap { 42 if label == "" { 43 return &v1.ConfigMap{ 44 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: legacytokentracking.ConfigMapName}, 45 } 46 } 47 return &v1.ConfigMap{ 48 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: legacytokentracking.ConfigMapName}, 49 Data: map[string]string{legacytokentracking.ConfigMapDataKey: label}, 50 } 51 } 52 53 func configuredServiceAccountTokenSecret(lastUsedLabel, invalidSinceLabel, creationTimeString, serviceAccountName, serviceAccountUID, deletionTimeString string) *v1.Secret { 54 var deletionTime *metav1.Time 55 if deletionTimeString == "" { 56 deletionTime = nil 57 } else { 58 deletionTime = &metav1.Time{Time: time.Now().UTC()} 59 } 60 creationTime, _ := time.Parse(dateFormat, creationTimeString) 61 labels := map[string]string{} 62 if lastUsedLabel != "" { 63 labels[serviceaccount.LastUsedLabelKey] = lastUsedLabel 64 } 65 if invalidSinceLabel != "" { 66 labels[serviceaccount.InvalidSinceLabelKey] = invalidSinceLabel 67 } 68 return &v1.Secret{ 69 ObjectMeta: metav1.ObjectMeta{ 70 Name: "token-secret-1", 71 Namespace: "default", 72 UID: "23456", 73 ResourceVersion: "1", 74 Labels: labels, 75 CreationTimestamp: metav1.NewTime(creationTime), 76 DeletionTimestamp: deletionTime, 77 Annotations: map[string]string{ 78 v1.ServiceAccountNameKey: serviceAccountName, 79 v1.ServiceAccountUIDKey: serviceAccountUID, 80 }, 81 }, 82 Type: v1.SecretTypeServiceAccountToken, 83 Data: map[string][]byte{ 84 "token": []byte("ABC"), 85 "ca.crt": []byte("CA Data"), 86 "namespace": []byte("default"), 87 }, 88 } 89 } 90 91 func configuredLegacyTokenCleanUpPeriod(start string) time.Duration { 92 current := time.Now().UTC() 93 startTime, _ := time.Parse(dateFormat, start) 94 return current.Sub(startTime) 95 } 96 97 func configuredPod(withSecretMount bool) *v1.Pod { 98 if !withSecretMount { 99 return &v1.Pod{ 100 ObjectMeta: metav1.ObjectMeta{ 101 Name: "pod-1", 102 Namespace: "default", 103 }, 104 } 105 } 106 return &v1.Pod{ 107 ObjectMeta: metav1.ObjectMeta{ 108 Name: "pod-1", 109 Namespace: "default", 110 }, 111 Spec: v1.PodSpec{ 112 Volumes: []v1.Volume{{Name: "foo", VolumeSource: v1.VolumeSource{Secret: &v1.SecretVolumeSource{SecretName: "token-secret-1"}}}}, 113 }, 114 } 115 } 116 117 func patchContent(namespace, name, invalidSince string, uID types.UID) []byte { 118 patch, _ := json.Marshal(applyv1.Secret(name, namespace).WithUID(uID).WithLabels(map[string]string{serviceaccount.InvalidSinceLabelKey: invalidSince})) 119 return patch 120 } 121 122 func TestLegacyServiceAccountTokenCleanUp(t *testing.T) { 123 testcases := map[string]struct { 124 LegacyTokenCleanUpPeriod time.Duration 125 126 ExistingServiceAccount *v1.ServiceAccount 127 ExistingSecret *v1.Secret 128 ExistingPod *v1.Pod 129 ClientObjects []runtime.Object 130 131 ExpectedActions []core.Action 132 }{ 133 "configmap does not exist": { 134 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""), 135 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 136 ExistingPod: configuredPod(false), 137 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-28"), 138 ExpectedActions: []core.Action{ 139 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 140 }, 141 }, 142 "configmap exists, but the configmap does not have tracked-since label": { 143 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""), 144 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 145 ExistingPod: configuredPod(false), 146 ClientObjects: []runtime.Object{configuredConfigMap("")}, 147 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-28"), 148 ExpectedActions: []core.Action{ 149 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 150 }, 151 }, 152 "configmap exists, the time period since 'tracked-since' is smaller than the CleanUpPeriod": { 153 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""), 154 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 155 ExistingPod: configuredPod(false), 156 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-29")}, 157 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 158 ExpectedActions: []core.Action{ 159 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 160 }, 161 }, 162 "configmap exists, the 'tracked-since' cannot be parsed": { 163 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""), 164 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 165 ExistingPod: configuredPod(false), 166 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27-1")}, 167 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 168 ExpectedActions: []core.Action{ 169 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 170 }, 171 }, 172 "secret is not SecretTypeServiceAccountToken type": { 173 ExistingSecret: opaqueSecret(), 174 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 175 ExistingPod: configuredPod(false), 176 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, 177 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 178 ExpectedActions: []core.Action{ 179 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 180 }, 181 }, 182 "secret is not referenced by serviceaccount": { 183 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""), 184 ExistingServiceAccount: serviceAccount(emptySecretReferences()), 185 ExistingPod: configuredPod(false), 186 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, 187 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 188 ExpectedActions: []core.Action{ 189 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 190 }, 191 }, 192 "auto-generated secret has a late creation time": { 193 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-30", "default", "12345", ""), 194 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 195 ExistingPod: configuredPod(false), 196 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, 197 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 198 ExpectedActions: []core.Action{ 199 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 200 }, 201 }, 202 "auto-generated secret has a deletion time": { 203 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", "deleted"), 204 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 205 ExistingPod: configuredPod(false), 206 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, 207 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"), 208 ExpectedActions: []core.Action{ 209 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 210 }, 211 }, 212 "auto-generated secret has a late last-used time": { 213 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-30", "", "2022-12-27", "default", "12345", ""), 214 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 215 ExistingPod: configuredPod(false), 216 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, 217 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 218 ExpectedActions: []core.Action{ 219 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 220 }, 221 }, 222 "auto-generated secret has a last-used label, but it can not be parsed": { 223 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27-1", "", "2022-12-27", "default", "12345", ""), 224 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 225 ExistingPod: configuredPod(false), 226 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, 227 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 228 ExpectedActions: []core.Action{ 229 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 230 }, 231 }, 232 "secret-referenced service account does not exist": { 233 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""), 234 ExistingPod: configuredPod(false), 235 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, 236 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 237 ExpectedActions: []core.Action{ 238 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 239 }, 240 }, 241 "secret-referenced service account uid does not match": { 242 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "123456", ""), 243 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 244 ExistingPod: configuredPod(false), 245 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, 246 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 247 ExpectedActions: []core.Action{ 248 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 249 }, 250 }, 251 "secret-referenced service account name is empty": { 252 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "", "12345", ""), 253 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 254 ExistingPod: configuredPod(false), 255 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-27")}, 256 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 257 ExpectedActions: []core.Action{ 258 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 259 }, 260 }, 261 "auto-generated secret does not have 'last-used' label, has not been marked as invalid": { 262 ExistingSecret: configuredServiceAccountTokenSecret("", "", "2022-12-27", "default", "12345", ""), 263 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 264 ExistingPod: configuredPod(false), 265 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, 266 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"), 267 ExpectedActions: []core.Action{ 268 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 269 core.NewPatchAction( 270 schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, 271 metav1.NamespaceDefault, "token-secret-1", 272 types.MergePatchType, 273 patchContent(metav1.NamespaceDefault, "token-secret-1", time.Now().UTC().Format(dateFormat), types.UID("23456")), 274 ), 275 }, 276 }, 277 "auto-generated secret does not have 'last-used' label, has been marked as invalid, invalid_since label can not be parsed": { 278 ExistingSecret: configuredServiceAccountTokenSecret("", "2022-12-29-1", "2022-12-27", "default", "12345", ""), 279 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 280 ExistingPod: configuredPod(false), 281 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, 282 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"), 283 ExpectedActions: []core.Action{ 284 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 285 core.NewPatchAction( 286 schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, 287 metav1.NamespaceDefault, "token-secret-1", 288 types.MergePatchType, 289 patchContent(metav1.NamespaceDefault, "token-secret-1", time.Now().UTC().Format(dateFormat), types.UID("23456")), 290 ), 291 }, 292 }, 293 "auto-generated secret does not have 'last-used' label, has been marked as invalid, time period since invalid is less than CleanUpPeriod": { 294 ExistingSecret: configuredServiceAccountTokenSecret("", "2023-01-01", "2022-12-27", "default", "12345", ""), 295 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 296 ExistingPod: configuredPod(false), 297 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, 298 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 299 ExpectedActions: []core.Action{ 300 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 301 }, 302 }, 303 "auto-generated secret does not have 'last-used' label, has been marked as invalid, time period since invalid is larger than CleanUpPeriod": { 304 ExistingSecret: configuredServiceAccountTokenSecret("", "2022-12-29", "2022-12-27", "default", "12345", ""), 305 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 306 ExistingPod: configuredPod(false), 307 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, 308 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2023-01-01"), 309 ExpectedActions: []core.Action{ 310 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 311 core.NewDeleteActionWithOptions( 312 schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, 313 metav1.NamespaceDefault, "token-secret-1", 314 metav1.DeleteOptions{ 315 Preconditions: &metav1.Preconditions{ResourceVersion: &configuredServiceAccountTokenSecret("", "2022-12-29", "2022-12-27", "default", "12345", "").ResourceVersion}, 316 }), 317 }, 318 }, 319 "auto-generated secret is mounted by the pod": { 320 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""), 321 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 322 ExistingPod: configuredPod(true), 323 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, 324 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-29"), 325 ExpectedActions: []core.Action{ 326 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 327 }, 328 }, 329 "auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod, secret has not been marked as invalid": { 330 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "", "2022-12-27", "default", "12345", ""), 331 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 332 ExistingPod: configuredPod(false), 333 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, 334 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"), 335 ExpectedActions: []core.Action{ 336 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 337 core.NewPatchAction( 338 schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, 339 metav1.NamespaceDefault, "token-secret-1", 340 types.MergePatchType, 341 patchContent(metav1.NamespaceDefault, "token-secret-1", time.Now().UTC().Format(dateFormat), types.UID("23456")), 342 ), 343 }, 344 }, 345 "auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod, secret has been marked as invalid, time peroid since invalid is less than CleanUpPeriod": { 346 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2023-05-01", "2022-12-27", "default", "12345", ""), 347 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 348 ExistingPod: configuredPod(false), 349 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, 350 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2022-12-30"), 351 ExpectedActions: []core.Action{ 352 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 353 }, 354 }, 355 "auto-generated secret has 'last-used' label, the time period since last-used is larger than CleanUpPeriod, secret has been marked as invalid, time peroid since invalid is larger than CleanUpPeriod": { 356 ExistingSecret: configuredServiceAccountTokenSecret("2022-12-27", "2023-01-05", "2022-12-27", "default", "12345", ""), 357 ExistingServiceAccount: serviceAccount(tokenSecretReferences()), 358 ExistingPod: configuredPod(false), 359 ClientObjects: []runtime.Object{configuredConfigMap("2022-12-28")}, 360 LegacyTokenCleanUpPeriod: configuredLegacyTokenCleanUpPeriod("2023-05-01"), 361 ExpectedActions: []core.Action{ 362 core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "configmaps"}, metav1.NamespaceSystem, legacytokentracking.ConfigMapName), 363 core.NewDeleteActionWithOptions( 364 schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, 365 metav1.NamespaceDefault, "token-secret-1", 366 metav1.DeleteOptions{ 367 Preconditions: &metav1.Preconditions{ResourceVersion: &configuredServiceAccountTokenSecret("", "2023-01-05", "2022-12-27", "default", "12345", "").ResourceVersion}, 368 }), 369 }, 370 }, 371 } 372 373 for k, tc := range testcases { 374 t.Run(k, func(t *testing.T) { 375 tc.ClientObjects = append(tc.ClientObjects, tc.ExistingSecret) 376 client := fake.NewSimpleClientset(tc.ClientObjects...) 377 378 informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc()) 379 secretInformer := informers.Core().V1().Secrets() 380 saInformer := informers.Core().V1().ServiceAccounts() 381 podInformer := informers.Core().V1().Pods() 382 secrets := secretInformer.Informer().GetStore() 383 serviceAccounts := saInformer.Informer().GetStore() 384 pods := podInformer.Informer().GetStore() 385 options := LegacySATokenCleanerOptions{ 386 SyncInterval: 30 * time.Second, 387 CleanUpPeriod: tc.LegacyTokenCleanUpPeriod, 388 } 389 cleaner, _ := NewLegacySATokenCleaner(saInformer, secretInformer, podInformer, client, testingclock.NewFakeClock(time.Now().UTC()), options) 390 391 if tc.ExistingServiceAccount != nil { 392 serviceAccounts.Add(tc.ExistingServiceAccount) 393 } 394 if tc.ExistingPod != nil { 395 pods.Add(tc.ExistingPod) 396 } 397 secrets.Add(tc.ExistingSecret) 398 399 ctx := context.TODO() 400 cleaner.evaluateSATokens(ctx) 401 402 actions := client.Actions() 403 if len(actions) != len(tc.ExpectedActions) { 404 t.Fatalf("got %d actions, wanted %d actions", len(actions), len(tc.ExpectedActions)) 405 } 406 for i, action := range actions { 407 if len(tc.ExpectedActions) < i+1 { 408 t.Errorf("%s: %d unexpected actions: %+v", k, len(actions)-len(tc.ExpectedActions), actions[i:]) 409 break 410 } 411 expectedAction := tc.ExpectedActions[i] 412 if !reflect.DeepEqual(expectedAction, action) { 413 t.Errorf("got action %#v, wanted %v", action, expectedAction) 414 } 415 } 416 }) 417 } 418 }