package k8s

import (
	"context"
	"errors"
	"fmt"
	"testing"

	"github.com/go-test/deep"
)

func TestResourceAuthz(t *testing.T) {
	tests := []struct {
		k8sConfigs []string
		err        error
	}{
		{
			// TODO: determine the objects that will affect ResourceAuthz
			[]string{`
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: cr-test
rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["list"]`,
				`
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: crb-test
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cr-test
subjects:
- kind: Group
  name: system:unauthenticated
  apiGroup: rbac.authorization.k8s.io`,
			},
			errors.New("not authorized to access deployments.apps"),
		},
	}

	ctx := context.Background()
	for i, test := range tests {
		test := test // pin
		t.Run(fmt.Sprintf("%d: returns expected authorization", i), func(t *testing.T) {
			k8sClient, err := NewFakeAPI(test.k8sConfigs...)
			if err != nil {
				t.Fatalf("Unexpected error: %s", err)
			}
			err = ResourceAuthz(ctx, k8sClient, "", "list", "apps", "v1", "deployments", "")
			if err != nil || test.err != nil {
				if (err == nil && test.err != nil) ||
					(err != nil && test.err == nil) ||
					(err.Error() != test.err.Error()) {
					t.Fatalf("Unexpected error (Expected: %s, Got: %s)", test.err, err)
				}
			}
		})
	}
}

func TestServiceProfilesAccess(t *testing.T) {
	fakeResources := []string{`
kind: APIResourceList
apiVersion: v1
groupVersion: linkerd.io/v1alpha2
resources:
- name: serviceprofiles
  singularName: serviceprofile
  namespaced: true
  kind: ServiceProfile
  verbs:
  - delete
  - deletecollection
  - get
  - list
  - patch
  - create
  - update
  - watch
  shortNames:
  - sp`}

	api, err := NewFakeAPI(fakeResources...)
	if err != nil {
		t.Fatalf("NewFakeAPI error: %s", err)
	}

	err = ServiceProfilesAccess(context.Background(), api)
	// RBAC SSAR request failed, but the Discovery lookup succeeded
	if diff := deep.Equal(err, errors.New("not authorized to access serviceprofiles.linkerd.io")); diff != nil {
		t.Errorf("%+v", diff)
	}
}

func TestServersAccess(t *testing.T) {
	testCases := []struct {
		name          string
		resources     []string
		expectedError error
	}{
		{
			name: "supports version but not authorized",
			resources: []string{
				`
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: servers.policy.linkerd.io
spec:
  conversion:
    strategy: None
  group: policy.linkerd.io
  names:
    kind: Server
    listKind: ServerList
    plural: servers
    shortNames:
    - srv
    singular: server
  scope: Namespaced
`, `
kind: APIResourceList
apiVersion: v1beta1
groupVersion: policy.linkerd.io/v1beta3
resources:
- name: servers
  singularName: server
  namespaced: true
  kind: Server
  verbs:
  - delete
  - deletecollection
  - get
  - list
  - patch
  - create
  - update
  - watch
  shortNames:
  - srv
`},
			expectedError: errors.New("not authorized to access servers.policy.linkerd.io"),
		},
		{
			name: "does not support version",
			resources: []string{
				`
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: servers.policy.linkerd.io
spec:
  conversion:
    strategy: None
  group: policy.linkerd.io
  names:
    kind: Server
    listKind: ServerList
    plural: servers
    shortNames:
    - srv
    singular: server
  scope: Namespaced
`, `
kind: APIResourceList
apiVersion: v1beta1
groupVersion: policy.linkerd.io/v1beta1
resources:
- name: servers
  singularName: server
  namespaced: true
  kind: Server
  verbs:
  - delete
  - deletecollection
  - get
  - list
  - patch
  - create
  - update
  - watch
  shortNames:
  - srv
`},
			expectedError: errors.New(`the server could not find the requested resource, GroupVersion "policy.linkerd.io/v1beta3" not found`),
		},
	}

	for _, tc := range testCases {
		tc := tc // pin
		t.Run(tc.name, func(t *testing.T) {
			api, err := NewFakeAPI(tc.resources...)
			if err != nil {
				t.Errorf("unexpected error: %s", err)
			}

			err = ServersAccess(context.Background(), api.Interface)
			if err == nil {
				t.Fatal("Expected error, but got success")
			}
			if err.Error() != tc.expectedError.Error() {
				t.Fatalf("Unexpected error (Expected: %s, Got: %s)", tc.expectedError, err)
			}
		})
	}
}
