package rbac

import "testing"

// build sets up an RBAC store with the given roles, then assigns them all to
// user "u".
func build(t *testing.T, roles ...Role) *RBAC {
	t.Helper()
	r := New()
	for _, role := range roles {
		r.DefineRole(role)
		r.AssignRole("u", role.Name)
	}
	return r
}

func TestCan(t *testing.T) {
	admin := Role{Name: "admin", ModuleGrants: map[string][]Permission{Wildcard: {All}}}
	reader := Role{Name: "reader", ModuleGrants: map[string][]Permission{Wildcard: {Read}}}
	sysWrite := Role{Name: "sysw", ModuleGrants: map[string][]Permission{"system": {Read, Write}}}

	tests := []struct {
		name   string
		role   Role
		module string
		perm   Permission
		want   bool
	}{
		{"wildcard module + wildcard perm", admin, "system", Root, true},
		{"wildcard module + wildcard perm, other module", admin, "services", Write, true},
		{"wildcard module, read only, read", reader, "anything", Read, true},
		{"wildcard module, read only, write denied", reader, "anything", Write, false},
		{"exact module exact perm", sysWrite, "system", Write, true},
		{"exact module missing perm", sysWrite, "system", Root, false},
		{"exact module, different module denied", sysWrite, "services", Read, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			r := build(t, tt.role)
			if got := r.Can("u", tt.module, tt.perm); got != tt.want {
				t.Errorf("Can(u, %q, %q) = %v, want %v", tt.module, tt.perm, got, tt.want)
			}
		})
	}
}

func TestCanNoRolesDenied(t *testing.T) {
	r := New()
	if r.Can("nobody", "system", Read) {
		t.Fatal("user with no roles was granted access")
	}
}

func TestCanAssignedUndefinedRoleSkipped(t *testing.T) {
	r := New()
	r.AssignRole("u", "ghost") // never defined
	if r.Can("u", "system", Read) {
		t.Fatal("undefined role granted access")
	}
}

func TestCanUnionOfRoles(t *testing.T) {
	r := build(t,
		Role{Name: "a", ModuleGrants: map[string][]Permission{"system": {Read}}},
		Role{Name: "b", ModuleGrants: map[string][]Permission{"services": {Write}}},
	)
	if !r.Can("u", "system", Read) || !r.Can("u", "services", Write) {
		t.Fatal("union of roles not honored")
	}
	if r.Can("u", "system", Write) {
		t.Fatal("permission leaked across modules")
	}
}

func TestRoleExists(t *testing.T) {
	r := New()
	r.DefineRole(Role{Name: "x"})
	if !r.RoleExists("x") {
		t.Error("defined role reported missing")
	}
	if r.RoleExists("y") {
		t.Error("undefined role reported present")
	}
}
