func statefulsetUpdate(namespace, name, targetService string) *unstructured.Unstructured {
	obj, err := decodeUnstructured(fmt.Sprintf(`{
    "kind": "StatefulSet",
    "apiVersion": "apps/v1beta1",
    "metadata": {
        "namespace": "%s",
        "name": "%s",
		"generation": 2,
        "labels": {
            "app": "foo"
        }
    },
    "spec": {
        "replicas": 2,
        "selector": {
            "matchLabels": {
                "app": "foo"
            }
        },
		"serviceName": "%s",
        "template": {
            "metadata": {
                "labels": {
                    "app": "foo"
                }
            },
            "spec": {
                "containers": [
                    {
                        "name": "nginx",
                        "image": "nginx:stable",
						"volumeMounts": [
							{
								"mountPath": "/usr/share/nginx/html",
								"name": "www"
							}
						]
                    }
                ],
				"terminationGracePeriodSeconds": 10
            }
        },
		"volumeClaimTemplates": [
			{
				"metadata": {
					"name": "www"
				},
				"spec": {
					"accessModes": [
						"ReadWriteOnce"
					],
					"resources": {
						"requests": {
							"storage": "1Gi"
						}
					}
				}
			}
		]
    }
}`, namespace, name, targetService))
	if err != nil {
		panic(err)
	}
	return obj
}
statefulsetReady is the state of the StatefulSet object after an update is issued through the API
package await
import (
	"fmt"
	"testing"
	"time"
	"github.com/stretchr/testify/assert"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/watch"
)
func Test_Apps_StatefulSet(t *testing.T) {
	const (
		inputNamespace = "default"
		inputName      = "foo"
		targetService  = "foo-service"
	)
	tests := []struct {
		description   string
		do            func(statefulsets, pods chan watch.Event, timeout chan time.Time)
		expectedError error
	}{
		{
			description: "[Revision 1] Should succeed after creating StatefulSet",
			do: func(statefulsets, pods chan watch.Event, timeout chan time.Time) {
				statefulsets <- watchAddedEvent(
					statefulsetAdded(inputNamespace, inputName, targetService))
				statefulsets <- watchAddedEvent(
					statefulsetCreating(inputNamespace, inputName, targetService))
				statefulsets <- watchAddedEvent(
					statefulsetProgressing(inputNamespace, inputName, targetService))
				statefulsets <- watchAddedEvent(
					statefulsetReady(inputNamespace, inputName, targetService))
				timeout <- time.Now()
			},
		},
		{
			description: "[Revision 2] Should succeed after updating StatefulSet",
			do: func(statefulsets, pods chan watch.Event, timeout chan time.Time) {
				statefulsets <- watchAddedEvent(
					statefulsetUpdate(inputNamespace, inputName, targetService))
				statefulsets <- watchAddedEvent(
					statefulsetUpdating(inputNamespace, inputName, targetService))
				statefulsets <- watchAddedEvent(
					statefulsetUpdatingWithActiveReplica(inputNamespace, inputName, targetService))
				statefulsets <- watchAddedEvent(
					statefulsetUpdateSuccess(inputNamespace, inputName, targetService))
				timeout <- time.Now()
			},
		},
		{
			description: "[Revision 1] Should fail if timeout occurs before successful creation",
			do: func(statefulsets, pods chan watch.Event, timeout chan time.Time) {
				statefulsets <- watchAddedEvent(
					statefulsetAdded(inputNamespace, inputName, targetService))
				statefulsets <- watchAddedEvent(
					statefulsetCreating(inputNamespace, inputName, targetService))
				timeout <- time.Now()
			},
			expectedError: &timeoutError{
				object: statefulsetCreating(inputNamespace, inputName, targetService),
				subErrors: []string{
					"0 out of 2 replicas succeeded readiness checks",
				}},
		},
		{
			description: "[Revision 2] Should fail if timeout occurs before successful update rollout",
			do: func(statefulsets, pods chan watch.Event, timeout chan time.Time) {
				statefulsets <- watchAddedEvent(
					statefulsetUpdate(inputNamespace, inputName, targetService))
				statefulsets <- watchAddedEvent(
					statefulsetUpdating(inputNamespace, inputName, targetService))
				timeout <- time.Now()
			},
			expectedError: &timeoutError{
				object: statefulsetUpdating(inputNamespace, inputName, targetService),
				subErrors: []string{
					"0 out of 2 replicas succeeded readiness checks",
					"StatefulSet controller failed to advance from revision \"foo-7b5cf87b78\" to revision \"foo-789c4b994f\"",
				}},
		},
		{
			description: "[Revision 1] Failure should only report Pods from active StatefulSet, part 1",
			do: func(statefulsets, pods chan watch.Event, timeout chan time.Time) {
				podName := "foo-0"
				statefulsets <- watchAddedEvent(
					statefulsetProgressing(inputNamespace, inputName, targetService))
				pods <- watchAddedEvent(statefulsetReadyPod(inputNamespace, podName, inputName))
				pods <- watchAddedEvent(statefulsetFailedPod(inputNamespace, podName, "bar"))
				timeout <- time.Now()
			},
			expectedError: &timeoutError{
				object: statefulsetProgressing(inputNamespace, inputName, targetService),
				subErrors: []string{
					"1 out of 2 replicas succeeded readiness checks",
				}},
		},
		{
			description: "[Revision 2] Failure should only report Pods from active StatefulSet, part 1",
			do: func(statefulsets, pods chan watch.Event, timeout chan time.Time) {
				podName := "foo-0"
				statefulsets <- watchAddedEvent(
					statefulsetUpdating(inputNamespace, inputName, targetService))
				pods <- watchAddedEvent(statefulsetReadyPod(inputNamespace, podName, inputName))
				pods <- watchAddedEvent(statefulsetFailedPod(inputNamespace, podName, "bar"))
				timeout <- time.Now()
			},
			expectedError: &timeoutError{
				object: statefulsetUpdating(inputNamespace, inputName, targetService),
				subErrors: []string{
					"0 out of 2 replicas succeeded readiness checks",
					"StatefulSet controller failed to advance from revision \"foo-7b5cf87b78\" to revision \"foo-789c4b994f\"",
				}},
		},
		{
			description: "[Revision 1] Failure should only report Pods from active StatefulSet, part 2",
			do: func(statefulsets, pods chan watch.Event, timeout chan time.Time) {
				podName := "foo-0"
				statefulsets <- watchAddedEvent(
					statefulsetProgressing(inputNamespace, inputName, targetService))
				pods <- watchAddedEvent(statefulsetFailedPod(inputNamespace, podName, inputName))
				pods <- watchAddedEvent(statefulsetReadyPod(inputNamespace, podName, "bar"))
				timeout <- time.Now()
			},
			expectedError: &timeoutError{
				object: statefulsetProgressing(inputNamespace, inputName, targetService),
				subErrors: []string{
					"1 out of 2 replicas succeeded readiness checks",
					"[Pod foo-0]: containers with unready status: [nginx] -- [ErrImagePull] manifest for nginx:busted not found",
				}},
		},
		{
			description: "[Revision 2] Failure should only report Pods from active StatefulSet, part 2",
			do: func(statefulsets, pods chan watch.Event, timeout chan time.Time) {
				podName := "foo-0"
				statefulsets <- watchAddedEvent(
					statefulsetUpdating(inputNamespace, inputName, targetService))
				pods <- watchAddedEvent(statefulsetFailedPod(inputNamespace, podName, inputName))
				pods <- watchAddedEvent(statefulsetReadyPod(inputNamespace, podName, "bar"))
				timeout <- time.Now()
			},
			expectedError: &timeoutError{
				object: statefulsetUpdating(inputNamespace, inputName, targetService),
				subErrors: []string{
					"0 out of 2 replicas succeeded readiness checks",
					"StatefulSet controller failed to advance from revision \"foo-7b5cf87b78\" to revision \"foo-789c4b994f\"",
					"[Pod foo-0]: containers with unready status: [nginx] -- [ErrImagePull] manifest for nginx:busted not found",
				}},
		},
	}
	for _, test := range tests {
		awaiter := makeStatefulSetInitAwaiter(
			updateAwaitConfig{
				createAwaitConfig: mockAwaitConfig(statefulsetInput(inputNamespace, inputName, targetService)),
			})
		statefulsets := make(chan watch.Event)
		pods := make(chan watch.Event)
		timeout := make(chan time.Time)
		period := make(chan time.Time)
		go test.do(statefulsets, pods, timeout)
		err := awaiter.await(statefulsets, pods, timeout, period)
		assert.Equal(t, test.expectedError, err, test.description)
	}
}
func Test_Apps_StatefulSet_MultipleUpdates(t *testing.T) {
	tests := []struct {
		description string
		inputs      func() *unstructured.Unstructured
		firstUpdate func(statefulsets, pods chan watch.Event, timeout chan time.Time,
			setLast setLastInputs)
		secondUpdate        func(statefulsets, pods chan watch.Event, timeout chan time.Time)
		firstExpectedError  error
		secondExpectedError error
	}{
		{
			description: "StatefulSet fails, is updated with working config, and then succeeds",
			inputs:      statefulsetFailed,
			firstUpdate: func(
				statefulsets, pods chan watch.Event, timeout chan time.Time,
				setLast setLastInputs,
			) {
				statefulsets <- watchAddedEvent(statefulsetFailed())
				setLast(statefulsetFailed())
				timeout <- time.Now()
			},
			firstExpectedError: &timeoutError{
				object: statefulsetFailed(),
				subErrors: []string{
					"0 out of 2 replicas succeeded readiness checks",
				}},
			secondUpdate: func(statefulset, pods chan watch.Event, timeout chan time.Time) {
				statefulset <- watchAddedEvent(statefulsetUpdatedAfterFailed())
				statefulset <- watchAddedEvent(statefulsetSucceedAfterFailed())
				timeout <- time.Now()
			},
		},
	}
	for _, test := range tests {
		awaiter := makeStatefulSetInitAwaiter(
			updateAwaitConfig{
				createAwaitConfig: mockAwaitConfig(test.inputs()),
			})
		statefulsets := make(chan watch.Event)
		pods := make(chan watch.Event)
		timeout := make(chan time.Time)
		period := make(chan time.Time)
		go test.firstUpdate(statefulsets, pods, timeout,
			func(obj *unstructured.Unstructured) {
				awaiter.config.lastInputs = obj
			})
		err := awaiter.await(statefulsets, pods, timeout, period)
		assert.Equal(t, test.firstExpectedError, err, test.description)
		statefulsets = make(chan watch.Event)
		pods = make(chan watch.Event)
		timeout = make(chan time.Time)
		period = make(chan time.Time)
		go test.secondUpdate(statefulsets, pods, timeout)
		err = awaiter.await(statefulsets, pods, timeout, period)
		assert.Equal(t, test.secondExpectedError, err, test.description)
	}
}
func Test_Apps_StatefulSetRead(t *testing.T) {
	const (
		inputNamespace = "default"
		inputName      = "foo"
		targetService  = "foo-service"
	)
	tests := []struct {
		description       string
		statefulset       func(namespace, name, targetService string) *unstructured.Unstructured
		expectedSubErrors []string
	}{
		{
			description: "Read should fail if StatefulSet status empty",
			statefulset: statefulsetAdded,
			expectedSubErrors: []string{
				"0 out of 2 replicas succeeded readiness checks",
				"StatefulSet controller failed to advance from revision \"\" to revision \"\"",
			},
		},
		{
			description: "Read should fail if StatefulSet is progressing",
			statefulset: statefulsetProgressing,
			expectedSubErrors: []string{
				"1 out of 2 replicas succeeded readiness checks",
			},
		},
		{
			description: "Read should succeed if StatefulSet is ready",
			statefulset: statefulsetReady,
		},
		{
			description: "Read should succeed if StatefulSet update is ready",
			statefulset: statefulsetUpdateSuccess,
		},
	}
	for _, test := range tests {
		awaiter := makeStatefulSetInitAwaiter(
			updateAwaitConfig{
				createAwaitConfig: mockAwaitConfig(statefulsetInput(inputNamespace, inputName, targetService)),
			})
		statefulset := test.statefulset(inputNamespace, inputName, targetService)
		err := awaiter.read(statefulset, unstructuredList())
		if test.expectedSubErrors != nil {
			assert.Equal(t, test.expectedSubErrors, err.(*initializationError).SubErrors(), test.description)
		} else {
			assert.Nil(t, err, test.description)
		}
	}
}
func statefulsetInput(namespace, name, targetService string) *unstructured.Unstructured {
	obj, err := decodeUnstructured(fmt.Sprintf(`{
    "kind": "StatefulSet",
    "apiVersion": "apps/v1beta1",
    "metadata": {
        "namespace": "%s",
        "name": "%s",
        "labels": {
            "app": "foo"
        }
    },
    "spec": {
        "replicas": 2,
        "selector": {
            "matchLabels": {
                "app": "foo"
            }
        },
		"serviceName": "%s",
        "template": {
            "metadata": {
                "labels": {
                    "app": "foo"
                }
            },
            "spec": {
                "containers": [
                    {
                        "name": "nginx",
                        "image": "nginx",
						"volumeMounts": [
							{
								"mountPath": "/usr/share/nginx/html",
								"name": "www"
							}
						]
                    }
                ],
				"terminationGracePeriodSeconds": 10
            }
        },
		"volumeClaimTemplates": [
			{
				"metadata": {
					"name": "www"
				},
				"spec": {
					"accessModes": [
						"ReadWriteOnce"
					],
					"resources": {
						"requests": {
							"storage": "1Gi"
						}
					}
				}
			}
		]
    }
}`, namespace, name, targetService))
	if err != nil {
		panic(err)
	}
	return obj
}
func statefulsetAdded(namespace, name, targetService string) *unstructured.Unstructured {
	obj, err := decodeUnstructured(fmt.Sprintf(`{
    "kind": "StatefulSet",
    "apiVersion": "apps/v1beta1",
    "metadata": {
        "namespace": "%s",
        "name": "%s",
		"generation": 1,
        "labels": {
            "app": "foo"
        }
    },
    "spec": {
        "replicas": 2,
        "selector": {
            "matchLabels": {
                "app": "foo"
            }
        },
		"serviceName": "%s",
        "template": {
            "metadata": {
                "labels": {
                    "app": "foo"
                }
            },
            "spec": {
                "containers": [
                    {
                        "name": "nginx",
                        "image": "nginx",
						"volumeMounts": [
							{
								"mountPath": "/usr/share/nginx/html",
								"name": "www"
							}
						]
                    }
                ],
				"terminationGracePeriodSeconds": 10
            }
        },
		"volumeClaimTemplates": [
			{
				"metadata": {
					"name": "www"
				},
				"spec": {
					"accessModes": [
						"ReadWriteOnce"
					],
					"resources": {
						"requests": {
							"storage": "1Gi"
						}
					}
				}
			}
		]
    },
	"status": {
        "replicas": 0
    }
}`, namespace, name, targetService))
	if err != nil {
		panic(err)
	}
	return obj
}
func statefulsetCreating(namespace, name, targetService string) *unstructured.Unstructured {
	obj, err := decodeUnstructured(fmt.Sprintf(`{
    "kind": "StatefulSet",
    "apiVersion": "apps/v1beta1",
    "metadata": {
        "namespace": "%s",
        "name": "%s",
		"generation": 1,
        "labels": {
            "app": "foo"
        }
    },
    "spec": {
        "replicas": 2,
        "selector": {
            "matchLabels": {
                "app": "foo"
            }
        },
		"serviceName": "%s",
        "template": {
            "metadata": {
                "labels": {
                    "app": "foo"
                }
            },
            "spec": {
                "containers": [
                    {
                        "name": "nginx",
                        "image": "nginx",
						"volumeMounts": [
							{
								"mountPath": "/usr/share/nginx/html",
								"name": "www"
							}
						]
                    }
                ],
				"terminationGracePeriodSeconds": 10
            }
        },
		"volumeClaimTemplates": [
			{
				"metadata": {
					"name": "www"
				},
				"spec": {
					"accessModes": [
						"ReadWriteOnce"
					],
					"resources": {
						"requests": {
							"storage": "1Gi"
						}
					}
				}
			}
		]
    },
	"status": {
		"replicas": 1,
		"collisionCount": 0,
		"currentReplicas": 1,
		"currentRevision": "foo-7b5cf87b78",
		"observedGeneration": 1,
		"updateRevision": "foo-7b5cf87b78"
    }
}`, namespace, name, targetService))
	if err != nil {
		panic(err)
	}
	return obj
}
func statefulsetProgressing(namespace, name, targetService string) *unstructured.Unstructured {
	obj, err := decodeUnstructured(fmt.Sprintf(`{
    "kind": "StatefulSet",
    "apiVersion": "apps/v1beta1",
    "metadata": {
        "namespace": "%s",
        "name": "%s",
		"generation": 1,
        "labels": {
            "app": "foo"
        }
    },
    "spec": {
        "replicas": 2,
        "selector": {
            "matchLabels": {
                "app": "foo"
            }
        },
		"serviceName": "%s",
        "template": {
            "metadata": {
                "labels": {
                    "app": "foo"
                }
            },
            "spec": {
                "containers": [
                    {
                        "name": "nginx",
                        "image": "nginx",
						"volumeMounts": [
							{
								"mountPath": "/usr/share/nginx/html",
								"name": "www"
							}
						]
                    }
                ],
				"terminationGracePeriodSeconds": 10
            }
        },
		"volumeClaimTemplates": [
			{
				"metadata": {
					"name": "www"
				},
				"spec": {
					"accessModes": [
						"ReadWriteOnce"
					],
					"resources": {
						"requests": {
							"storage": "1Gi"
						}
					}
				}
			}
		]
    },
	"status": {
		"replicas": 2,
		"collisionCount": 0,
		"currentReplicas": 2,
		"currentRevision": "foo-7b5cf87b78",
		"observedGeneration": 1,
		"updateRevision": "foo-7b5cf87b78",
		"readyReplicas": 1
    }
}`, namespace, name, targetService))
	if err != nil {
		panic(err)
	}
	return obj
}
func statefulsetReady(namespace, name, targetService string) *unstructured.Unstructured {
	obj, err := decodeUnstructured(fmt.Sprintf(`{
    "kind": "StatefulSet",
    "apiVersion": "apps/v1beta1",
    "metadata": {
        "namespace": "%s",
        "name": "%s",
		"generation": 1,
        "labels": {
            "app": "foo"
        }
    },
    "spec": {
        "replicas": 2,
        "selector": {
            "matchLabels": {
                "app": "foo"
            }
        },
		"serviceName": "%s",
        "template": {
            "metadata": {
                "labels": {
                    "app": "foo"
                }
            },
            "spec": {
                "containers": [
                    {
                        "name": "nginx",
                        "image": "nginx",
						"volumeMounts": [
							{
								"mountPath": "/usr/share/nginx/html",
								"name": "www"
							}
						]
                    }
                ],
				"terminationGracePeriodSeconds": 10
            }
        },
		"volumeClaimTemplates": [
			{
				"metadata": {
					"name": "www"
				},
				"spec": {
					"accessModes": [
						"ReadWriteOnce"
					],
					"resources": {
						"requests": {
							"storage": "1Gi"
						}
					}
				}
			}
		]
    },
	"status": {
		"replicas": 2,
		"collisionCount": 0,
		"currentReplicas": 2,
		"currentRevision": "foo-7b5cf87b78",
		"observedGeneration": 1,
		"updateRevision": "foo-7b5cf87b78",
		"readyReplicas": 2
    }
}`, namespace, name, targetService))
	if err != nil {
		panic(err)
	}
	return obj
}