/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// This code is directly lifted from the kubefed codebase.
// For reference:
// https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/sync/dispatch/retain_test.go#L91-L292

package lifted

import (
	"reflect"
	"testing"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/utils/pointer"
)

// +lifted:source=https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/sync/dispatch/retain_test.go#L91-L174
// +lifted:changed

func TestRetainHealthCheckNodePortInServiceFields(t *testing.T) {
	tests := []struct {
		name          string
		desiredObj    *unstructured.Unstructured
		clusterObj    *unstructured.Unstructured
		retainSucceed bool
		expectedValue *int64
	}{
		{
			"cluster object has no healthCheckNodePort",
			&unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			&unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			true,
			nil,
		},
		{
			"cluster object has invalid healthCheckNodePort",
			&unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			&unstructured.Unstructured{
				Object: map[string]interface{}{
					"spec": map[string]interface{}{
						"healthCheckNodePort": "invalid string",
					},
				},
			},
			false,
			nil,
		},
		{
			"cluster object has healthCheckNodePort 0",
			&unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			&unstructured.Unstructured{
				Object: map[string]interface{}{
					"spec": map[string]interface{}{
						"healthCheckNodePort": int64(0),
					},
				},
			},
			true,
			nil,
		},
		{
			"cluster object has healthCheckNodePort 1000",
			&unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			&unstructured.Unstructured{
				Object: map[string]interface{}{
					"spec": map[string]interface{}{
						"healthCheckNodePort": int64(1000),
					},
				},
			},
			true,
			pointer.Int64(1000),
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if _, err := RetainServiceFields(test.desiredObj, test.clusterObj); (err == nil) != test.retainSucceed {
				t.Fatalf("test %s fails: unexpected returned error %v", test.name, err)
			}

			currentValue, ok, err := unstructured.NestedInt64(test.desiredObj.Object, "spec", "healthCheckNodePort")
			if err != nil {
				t.Fatalf("test %s fails: %v", test.name, err)
			}
			if !ok && test.expectedValue != nil {
				t.Fatalf("test %s fails: expect specified healthCheckNodePort but not found", test.name)
			}
			if ok && (test.expectedValue == nil || *test.expectedValue != currentValue) {
				t.Fatalf("test %s fails: unexpected current healthCheckNodePort %d", test.name, currentValue)
			}
		})
	}
}

// +lifted:source=https://github.com/kubernetes-sigs/kubefed/blob/master/pkg/controller/sync/dispatch/retain_test.go#L176-L292
// +lifted:changed

func TestRetainClusterIPInServiceFields(t *testing.T) {
	tests := []struct {
		name                   string
		desiredObj             *unstructured.Unstructured
		clusterObj             *unstructured.Unstructured
		retainSucceed          bool
		expectedClusterIPValue *string
	}{
		{
			"cluster object has no clusterIP",
			&unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			&unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			true,
			nil,
		},
		{
			"cluster object has invalid clusterIP",
			&unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			&unstructured.Unstructured{
				Object: map[string]interface{}{
					"spec": map[string]interface{}{
						"clusterIP": -1000,
					},
				},
			},
			false,
			nil,
		},
		{
			"cluster object has valid clusterIP",
			&unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			&unstructured.Unstructured{
				Object: map[string]interface{}{
					"spec": map[string]interface{}{
						"clusterIP": "1.2.3.4",
					},
				},
			},
			true,
			pointer.String("1.2.3.4"),
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if _, err := RetainServiceFields(test.desiredObj, test.clusterObj); (err == nil) != test.retainSucceed {
				t.Fatalf("test %s fails: unexpected returned error %v", test.name, err)
			}

			currentClusterIPValue, ok, err := unstructured.NestedString(test.desiredObj.Object, "spec", "clusterIP")
			if err != nil {
				t.Fatalf("test %s fails: %v", test.name, err)
			}
			if !ok && test.expectedClusterIPValue != nil {
				t.Fatalf("test %s fails: expect specified clusterIP but not found", test.name)
			}
			if ok && (test.expectedClusterIPValue == nil || *test.expectedClusterIPValue != currentClusterIPValue) {
				t.Fatalf("test %s fails: unexpected current clusterIP %s", test.name, currentClusterIPValue)
			}
		})
	}
}

func TestRetainServiceAccountFields(t *testing.T) {
	tests := []struct {
		name                 string
		desiredObj           *unstructured.Unstructured
		observedObj          *unstructured.Unstructured
		expectedErr          bool
		expectedSecretsValue []interface{}
	}{
		{
			name: "neither desired or observed service account has the secrets field",
			desiredObj: &unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			observedObj: &unstructured.Unstructured{
				Object: map[string]interface{}{},
			},
			expectedErr:          false,
			expectedSecretsValue: nil,
		},
		{
			name: "both desired and observed service account have the same secrets field",
			desiredObj: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"secrets": []interface{}{
						map[string]interface{}{
							"name": "test",
						},
					},
				},
			},
			observedObj: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"secrets": []interface{}{
						map[string]interface{}{
							"name": "test",
						},
					},
				},
			},
			expectedErr: false,
			expectedSecretsValue: []interface{}{
				map[string]interface{}{
					"name": "test",
				},
			},
		},
		{
			name: "desired and observed service account have the different secrets field",
			desiredObj: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"secrets": []interface{}{
						map[string]interface{}{
							"name": "test",
						},
					},
				},
			},
			observedObj: &unstructured.Unstructured{
				Object: map[string]interface{}{
					"secrets": []interface{}{
						map[string]interface{}{
							"name": "test",
						},
						map[string]interface{}{
							"name": "test-token",
						},
					},
				},
			},
			expectedErr: false,
			expectedSecretsValue: []interface{}{
				map[string]interface{}{
					"name": "test",
				},
				map[string]interface{}{
					"name": "test-token",
				},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			desiredObj, err := RetainServiceAccountFields(test.desiredObj, test.observedObj)
			if (err != nil) != test.expectedErr {
				t.Fatalf("unexpected returned error %v", err)
			}

			if err == nil {
				currentSecretValue, ok, err := unstructured.NestedSlice(desiredObj.Object, SecretsField)
				if err != nil {
					t.Fatalf("failed to get the secrets field from the serviceaccount, err is: %v", err)
				}
				if !ok && test.expectedSecretsValue != nil {
					t.Fatalf("expect specified secrets %s but not found", test.expectedSecretsValue)
				}
				if ok && !reflect.DeepEqual(test.expectedSecretsValue, currentSecretValue) {
					t.Fatalf("expect specified secrets %s but get %s", test.expectedSecretsValue, currentSecretValue)
				}
			}
		})
	}
}
