package rulevalidation

import (
	"reflect"
	"sort"
	"testing"

	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/unversioned"
	"k8s.io/kubernetes/pkg/util/sets"

	"github.com/openshift/origin/pkg/authorization/api"
)

func TestCompactRules(t *testing.T) {
	testcases := map[string]struct {
		Rules    []api.PolicyRule
		Expected []api.PolicyRule
	}{
		"empty": {
			Rules:    []api.PolicyRule{},
			Expected: []api.PolicyRule{},
		},
		"simple": {
			Rules: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds")},
				{Verbs: sets.NewString("list"), APIGroups: []string{""}, Resources: sets.NewString("builds")},
				{Verbs: sets.NewString("update", "patch"), APIGroups: []string{""}, Resources: sets.NewString("builds")},

				{Verbs: sets.NewString("create"), APIGroups: []string{"extensions"}, Resources: sets.NewString("daemonsets")},
				{Verbs: sets.NewString("delete"), APIGroups: []string{"extensions"}, Resources: sets.NewString("daemonsets")},

				{Verbs: sets.NewString("educate"), APIGroups: []string{""}, Resources: sets.NewString("dolphins")},

				// nil verbs are preserved in non-merge cases.
				// these are the pirates who don't do anything.
				{Verbs: nil, APIGroups: []string{""}, Resources: sets.NewString("pirates")},

				// Test merging into a nil Verbs string set
				{Verbs: nil, APIGroups: []string{""}, Resources: sets.NewString("pods")},
				{Verbs: sets.NewString("create"), APIGroups: []string{""}, Resources: sets.NewString("pods")},
			},
			Expected: []api.PolicyRule{
				{Verbs: sets.NewString("create", "delete"), APIGroups: []string{"extensions"}, Resources: sets.NewString("daemonsets")},
				{Verbs: sets.NewString("get", "list", "update", "patch"), APIGroups: []string{""}, Resources: sets.NewString("builds")},
				{Verbs: sets.NewString("educate"), APIGroups: []string{""}, Resources: sets.NewString("dolphins")},
				{Verbs: nil, APIGroups: []string{""}, Resources: sets.NewString("pirates")},
				{Verbs: sets.NewString("create"), APIGroups: []string{""}, Resources: sets.NewString("pods")},
			},
		},
		"complex multi-group": {
			Rules: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{"", "builds.openshift.io"}, Resources: sets.NewString("builds")},
				{Verbs: sets.NewString("list"), APIGroups: []string{"", "builds.openshift.io"}, Resources: sets.NewString("builds")},
			},
			Expected: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{"", "builds.openshift.io"}, Resources: sets.NewString("builds")},
				{Verbs: sets.NewString("list"), APIGroups: []string{"", "builds.openshift.io"}, Resources: sets.NewString("builds")},
			},
		},

		"complex multi-resource": {
			Rules: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds", "images")},
				{Verbs: sets.NewString("list"), APIGroups: []string{""}, Resources: sets.NewString("builds", "images")},
			},
			Expected: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds", "images")},
				{Verbs: sets.NewString("list"), APIGroups: []string{""}, Resources: sets.NewString("builds", "images")},
			},
		},

		"complex named-resource": {
			Rules: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), ResourceNames: sets.NewString("mybuild")},
				{Verbs: sets.NewString("list"), APIGroups: []string{""}, Resources: sets.NewString("builds"), ResourceNames: sets.NewString("mybuild2")},
			},
			Expected: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), ResourceNames: sets.NewString("mybuild")},
				{Verbs: sets.NewString("list"), APIGroups: []string{""}, Resources: sets.NewString("builds"), ResourceNames: sets.NewString("mybuild2")},
			},
		},

		"complex non-resource": {
			Rules: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), NonResourceURLs: sets.NewString("/")},
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), NonResourceURLs: sets.NewString("/foo")},
			},
			Expected: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), NonResourceURLs: sets.NewString("/")},
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), NonResourceURLs: sets.NewString("/foo")},
			},
		},

		"complex attributes": {
			Rules: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), AttributeRestrictions: &api.IsPersonalSubjectAccessReview{}},
				{Verbs: sets.NewString("list"), APIGroups: []string{""}, Resources: sets.NewString("builds"), AttributeRestrictions: &api.IsPersonalSubjectAccessReview{}},
			},
			Expected: []api.PolicyRule{
				{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), AttributeRestrictions: &api.IsPersonalSubjectAccessReview{}},
				{Verbs: sets.NewString("list"), APIGroups: []string{""}, Resources: sets.NewString("builds"), AttributeRestrictions: &api.IsPersonalSubjectAccessReview{}},
			},
		},
	}

	for k, tc := range testcases {
		rules := tc.Rules
		originalRules, err := kapi.Scheme.DeepCopy(tc.Rules)
		if err != nil {
			t.Errorf("%s: couldn't copy rules: %v", k, err)
			continue
		}
		compacted, err := CompactRules(tc.Rules)
		if err != nil {
			t.Errorf("%s: unexpected error: %v", k, err)
			continue
		}
		if !reflect.DeepEqual(rules, originalRules) {
			t.Errorf("%s: CompactRules mutated rules. Expected\n%#v\ngot\n%#v", k, originalRules, rules)
			continue
		}
		if covers, missing := Covers(compacted, rules); !covers {
			t.Errorf("%s: compacted rules did not cover original rules. missing: %#v", k, missing)
			continue
		}
		if covers, missing := Covers(rules, compacted); !covers {
			t.Errorf("%s: original rules did not cover compacted rules. missing: %#v", k, missing)
			continue
		}

		sort.Stable(api.SortableRuleSlice(compacted))
		sort.Stable(api.SortableRuleSlice(tc.Expected))
		if !reflect.DeepEqual(compacted, tc.Expected) {
			t.Errorf("%s: Expected\n%#v\ngot\n%#v", k, tc.Expected, compacted)
			continue
		}
	}
}

func TestIsSimpleResourceRule(t *testing.T) {
	testcases := map[string]struct {
		Rule     api.PolicyRule
		Simple   bool
		Resource unversioned.GroupResource
	}{
		"simple, no verbs": {
			Rule:     api.PolicyRule{Verbs: sets.NewString(), APIGroups: []string{""}, Resources: sets.NewString("builds")},
			Simple:   true,
			Resource: unversioned.GroupResource{Group: "", Resource: "builds"},
		},
		"simple, one verb": {
			Rule:     api.PolicyRule{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds")},
			Simple:   true,
			Resource: unversioned.GroupResource{Group: "", Resource: "builds"},
		},
		"simple, multi verb": {
			Rule:     api.PolicyRule{Verbs: sets.NewString("get", "list"), APIGroups: []string{""}, Resources: sets.NewString("builds")},
			Simple:   true,
			Resource: unversioned.GroupResource{Group: "", Resource: "builds"},
		},

		"complex, empty": {
			Rule:     api.PolicyRule{},
			Simple:   false,
			Resource: unversioned.GroupResource{},
		},
		"complex, no group": {
			Rule:     api.PolicyRule{Verbs: sets.NewString("get"), APIGroups: []string{}, Resources: sets.NewString("builds")},
			Simple:   false,
			Resource: unversioned.GroupResource{},
		},
		"complex, multi group": {
			Rule:     api.PolicyRule{Verbs: sets.NewString("get"), APIGroups: []string{"a", "b"}, Resources: sets.NewString("builds")},
			Simple:   false,
			Resource: unversioned.GroupResource{},
		},
		"complex, no resource": {
			Rule:     api.PolicyRule{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString()},
			Simple:   false,
			Resource: unversioned.GroupResource{},
		},
		"complex, multi resource": {
			Rule:     api.PolicyRule{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds", "images")},
			Simple:   false,
			Resource: unversioned.GroupResource{},
		},
		"complex, resource names": {
			Rule:     api.PolicyRule{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), ResourceNames: sets.NewString("foo")},
			Simple:   false,
			Resource: unversioned.GroupResource{},
		},
		"complex, attribute restrictions": {
			Rule:     api.PolicyRule{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), AttributeRestrictions: &api.IsPersonalSubjectAccessReview{}},
			Simple:   false,
			Resource: unversioned.GroupResource{},
		},
		"complex, non-resource urls": {
			Rule:     api.PolicyRule{Verbs: sets.NewString("get"), APIGroups: []string{""}, Resources: sets.NewString("builds"), NonResourceURLs: sets.NewString("/")},
			Simple:   false,
			Resource: unversioned.GroupResource{},
		},
	}

	for k, tc := range testcases {
		resource, simple := isSimpleResourceRule(&tc.Rule)
		if simple != tc.Simple {
			t.Errorf("%s: expected simple=%v, got simple=%v", k, tc.Simple, simple)
			continue
		}
		if resource != tc.Resource {
			t.Errorf("%s: expected resource=%v, got resource=%v", k, tc.Resource, resource)
			continue
		}
	}
}