UNPKG

@eturino/claims

Version:

Claim, ClaimSet and Ability for permissions (Typescript port of https://github.com/eturino/claims)

257 lines (212 loc) 10.3 kB
import { describe, expect, it, vi } from "vitest"; import { KeySetAll, KeySetAllExceptSome, KeySetNone, KeySetSome } from "@eturino/key-set"; import { Ability, ClaimSet, buildAbility, buildClaim, buildClaimSet } from "../../.."; describe("buildAbility()", () => { it('with [""] and ["read:valid"]: error', () => { expect(() => buildAbility([""], ["read:valid"])).toThrowError(); }); it('with ["read:valid"] and [""]: error', () => { expect(() => buildAbility(["read:valid"], [""])).toThrowError(); }); it('with ["read:*"] and ["read:something"]: Ability with ClaimSets', () => { const firstClaim = buildClaim("read:*"); const secondClaim = buildClaim("read:something"); const ability = buildAbility(["read:*"], ["read:something"]); expect(ability).toBeInstanceOf(Ability); expect(ability.permitted).toBeInstanceOf(ClaimSet); expect(ability.permitted.claims).toEqual([firstClaim]); expect(ability.prohibited).toBeInstanceOf(ClaimSet); expect(ability.prohibited.claims).toEqual([secondClaim]); expect(ability.cacheID).toEqual('(["read:*"],["read:something"])'); }); }); describe("Ability#clone", () => { it("makes a clone", () => { const firstClaim = buildClaim("read:*"); const secondClaim = buildClaim("read:something"); const ability = buildAbility(["read:*"], ["read:something"]); expect(ability).toBeInstanceOf(Ability); expect(ability.permitted).toBeInstanceOf(ClaimSet); expect(ability.permitted.claims).toEqual([firstClaim]); expect(ability.prohibited).toBeInstanceOf(ClaimSet); expect(ability.prohibited.claims).toEqual([secondClaim]); expect(ability.cacheID).toEqual('(["read:*"],["read:something"])'); const cloned = ability.clone(); expect(cloned.cacheID).toEqual(ability.cacheID); expect(cloned).toEqual(ability); expect(cloned).not.toBe(ability); expect(cloned.permitted).toEqual(ability.permitted); expect(cloned.permitted).not.toBe(ability.permitted); expect(cloned.prohibited).toEqual(ability.prohibited); expect(cloned.prohibited).not.toBe(ability.prohibited); }); }); describe("Ability#can", () => { it("returns true if permitted.check() and NOT prohibited.check()", () => { const permitted = buildClaimSet([]); const prohibited = buildClaimSet([]); const ability = new Ability(permitted, prohibited); const permittedFn = vi.fn(() => true); const prohibitedFn = vi.fn(() => false); permitted.check = permittedFn; prohibited.check = prohibitedFn; expect(ability.can("read:whatever")).toBeTruthy(); // uses mocks! expect(permittedFn.mock.calls.length).toBe(1); expect(prohibitedFn.mock.calls.length).toBe(1); }); }); describe("Ability#cannot", () => { it("inverse of #can", () => { const permitted = buildClaimSet([]); const prohibited = buildClaimSet([]); const ability = new Ability(permitted, prohibited); const permittedFn = vi.fn(() => true); const prohibitedFn = vi.fn(() => false); permitted.check = permittedFn; prohibited.check = prohibitedFn; expect(ability.cannot("read:whatever")).toBeFalsy(); // uses mocks! expect(permittedFn.mock.calls.length).toBe(1); expect(prohibitedFn.mock.calls.length).toBe(1); }); }); describe("Ability#isExplicitlyProhibited", () => { it("return true if prohibited.check()", () => { const permitted = buildClaimSet([]); const prohibited = buildClaimSet([]); const ability = new Ability(permitted, prohibited); const permittedFn = vi.fn(() => true); const prohibitedFn = vi.fn(() => true); permitted.check = permittedFn; prohibited.check = prohibitedFn; expect(ability.isExplicitlyProhibited("read:whatever")).toBeTruthy(); // uses mocks! expect(permittedFn.mock.calls.length).toBe(0); expect(prohibitedFn.mock.calls.length).toBe(1); }); it("regardless of permitted", () => { const permitted = buildClaimSet([]); const prohibited = buildClaimSet([]); const ability = new Ability(permitted, prohibited); const permittedFn = vi.fn(() => false); const prohibitedFn = vi.fn(() => false); permitted.check = permittedFn; prohibited.check = prohibitedFn; expect(ability.isExplicitlyProhibited("read:whatever")).toBeFalsy(); // uses mocks! expect(permittedFn.mock.calls.length).toBe(0); expect(prohibitedFn.mock.calls.length).toBe(1); }); }); describe("Ability#isExplicitlyProhibited", () => { it("return true if prohibited.check()", () => { const permitted = buildClaimSet([]); const prohibited = buildClaimSet([]); const ability = new Ability(permitted, prohibited); const permittedFn = vi.fn(() => true); const prohibitedFn = vi.fn(() => true); permitted.check = permittedFn; prohibited.check = prohibitedFn; expect(ability.isExplicitlyProhibited("read:whatever")).toBeTruthy(); // uses mocks! expect(permittedFn.mock.calls.length).toBe(0); expect(prohibitedFn.mock.calls.length).toBe(1); }); it("regardless of permitted", () => { const permitted = buildClaimSet([]); const prohibited = buildClaimSet([]); const ability = new Ability(permitted, prohibited); const permittedFn = vi.fn(() => false); const prohibitedFn = vi.fn(() => false); permitted.check = permittedFn; prohibited.check = prohibitedFn; expect(ability.isExplicitlyProhibited("read:whatever")).toBeFalsy(); // uses mocks! expect(permittedFn.mock.calls.length).toBe(0); expect(prohibitedFn.mock.calls.length).toBe(1); }); }); describe("Ability#accessToResources", () => { describe('accessToResources("read:clients")', () => { describe('permitted: ["read:clients.*"], prohibited: []', () => { it("returns KeySetAll", () => { const ability = buildAbility(["read:clients.*"], []); expect(ability.accessToResources("read:clients")).toBeInstanceOf(KeySetAll); }); }); describe('permitted: ["read:clients.*"], prohibited: ["read:clients"]', () => { it("returns KeySetNone", () => { const ability = buildAbility(["read:clients.*"], ["read:clients"]); expect(ability.accessToResources("read:clients")).toBeInstanceOf(KeySetNone); }); }); describe('permitted: ["read:clients.*"], prohibited: ["read:clients.first", "read:clients.second"]', () => { it("returns KeySetAllExceptSome(first,second)", () => { const ability = buildAbility(["read:clients.*"], ["read:clients.first", "read:clients.second"]); const keySet = ability.accessToResources("read:clients"); expect(keySet).toBeInstanceOf(KeySetAllExceptSome); expect((keySet as KeySetAllExceptSome<string>).elementsList).toEqual(["first", "second"]); }); }); describe('permitted: ["read:clients.first.some.stuff", "read:clients.third.other.things"], prohibited: ["read:clients.first", "read:clients.second"]', () => { it("returns KeySetSome(third)", () => { const ability = buildAbility( ["read:clients.first.some.stuff", "read:clients.third.other.things"], ["read:clients.first", "read:clients.second"], ); const keySet = ability.accessToResources("read:clients"); expect(keySet).toBeInstanceOf(KeySetSome); expect((keySet as KeySetSome<string>).elementsList).toEqual(["third"]); }); }); describe('permitted: ["read:clients.first", "read:clients.third"], prohibited: ["read:clients"]', () => { it("returns KeySetNone", () => { const ability = buildAbility(["read:clients.first", "read:clients.third"], ["read:clients"]); const keySet = ability.accessToResources("read:clients"); expect(keySet).toBeInstanceOf(KeySetNone); }); }); describe('permitted: ["read:clients.first", "read:clients.third"], prohibited: ["read:clients.third.people"]', () => { it("returns KeySetSome(first, third)", () => { const ability = buildAbility(["read:clients.first", "read:clients.third"], ["read:clients.third.people"]); const keySet = ability.accessToResources("read:clients"); expect(keySet).toBeInstanceOf(KeySetSome); expect((keySet as KeySetSome<string>).elementsList).toEqual(["first", "third"]); }); }); }); describe("accessToResources('read:clients.my-client.projects.project')", () => { describe('permitted: ["read:clients.*"], prohibited: []', () => { it("returns KeySetAll", () => { const ability = buildAbility(["read:clients.*"], []); expect(ability.accessToResources("read:clients.my-client.projects.project")).toBeInstanceOf(KeySetAll); }); }); describe('permitted: ["read:clients.*"], prohibited: ["read:clients.my-client.projects"]', () => { it("returns KeySetNone", () => { const ability = buildAbility(["read:clients.*"], ["read:clients.my-client.projects"]); expect(ability.accessToResources("read:clients.my-client.projects.project")).toBeInstanceOf(KeySetNone); }); }); describe('permitted: ["read:clients.*"], prohibited: ["read:clients.my-client.projects.project.bad-project"]', () => { it("returns KeySetAllExceptSome", () => { const ability = buildAbility(["read:clients.*"], ["read:clients.my-client.projects.project.bad-project"]); const keySet = ability.accessToResources("read:clients.my-client.projects.project"); expect(keySet).toBeInstanceOf(KeySetAllExceptSome); expect((keySet as KeySetAllExceptSome<string>).elementsList).toEqual(["bad-project"]); }); }); describe("prohibiting people in one of the projects", () => { it("it still counts that project", () => { const ability = buildAbility( [ "read:clients.my-client.projects.project.one-project", "read:clients.my-client.projects.project.bad-project", ], [ "read:clients.my-client.projects.project.one-project.people", "read:clients.my-client.projects.project.bad-project", ], ); const keySet = ability.accessToResources("read:clients.my-client.projects.project"); expect(keySet).toBeInstanceOf(KeySetSome); expect((keySet as KeySetSome<string>).elementsList).toEqual(["one-project"]); }); }); }); });