@jperezmart/nest-casl-testing 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,9 +23,15 @@ would have built:
23
23
 
24
24
  ```ts
25
25
  import { buildAbilityForTest } from '@jperezmart/nest-casl-testing';
26
+ import type { RolePermissions } from '@jperezmart/nest-casl';
26
27
  import { subject } from '@casl/ability';
27
28
 
28
- const permissions = {
29
+ interface AppUser {
30
+ id: string;
31
+ roles: string[];
32
+ }
33
+
34
+ const permissions: RolePermissions<'author', AppUser> = {
29
35
  author: (user, { can }) => {
30
36
  can('read', 'Article');
31
37
  can('update', 'Article', { authorId: user.id });
@@ -55,8 +61,10 @@ application would:
55
61
  | `detectSubjectType` | `(subject: object) => string` | Custom subject-type detection, for subjects CASL cannot name on its own. |
56
62
 
57
63
  A role whose definition is `true` rather than a function also grants
58
- `manage all`. A user whose `roles` is missing or not an array is treated as
59
- having no roles, which is what `AbilityFactory` does.
64
+ `manage all`. The `everyone` entry applies to every user before their Roles,
65
+ and a role named `everyone` on the user is ignored. A user whose `roles` is
66
+ missing or not an array is treated as having no roles (the Everyone
67
+ permissions still apply), which is what `AbilityFactory` does.
60
68
 
61
69
  ## License
62
70
 
package/dist/index.cjs CHANGED
@@ -2,9 +2,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _casl_ability = require("@casl/ability");
3
3
  //#region src/index.ts
4
4
  /**
5
- * Build a CASL ability directly from a permissions map and a user, without
5
+ * Build a CASL ability directly from Role permissions and a user, without
6
6
  * booting a Nest application. Intended for unit-testing permission definitions
7
- * in projects that consume `@jperezmart/nest-casl`.
7
+ * in projects that consume `@jperezmart/nest-casl`. The user and ability types
8
+ * are inferred from `permissions`; the user may carry roles the map does not
9
+ * declare. Everyone permissions apply first, then the user's Roles.
8
10
  *
9
11
  * @example
10
12
  * ```ts
@@ -20,10 +22,14 @@ function buildAbilityForTest(permissions, user, options = {}) {
20
22
  builder.can("manage", "all");
21
23
  return builder.build(buildOptions);
22
24
  }
25
+ const abilityBuilder = builder;
26
+ const everyone = permissions["everyone"];
27
+ if (typeof everyone === "function") everyone(user, abilityBuilder);
23
28
  for (const role of roles) {
29
+ if (role === "everyone") continue;
24
30
  const definition = permissions[role];
25
31
  if (definition === true) builder.can("manage", "all");
26
- else if (typeof definition === "function") definition(user, builder);
32
+ else if (typeof definition === "function") definition(user, abilityBuilder);
27
33
  }
28
34
  return builder.build(buildOptions);
29
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["AbilityBuilder","createMongoAbility"],"sources":["../src/index.ts"],"sourcesContent":["import type { AnyAbility, MongoAbility } from '@casl/ability';\nimport { AbilityBuilder, createMongoAbility } from '@casl/ability';\nimport type {\n AuthorizableUser,\n DefinePermissions,\n Permissions,\n} from '@jperezmart/nest-casl';\n\n/** Extra options accepted by {@link buildAbilityForTest}. */\nexport interface BuildAbilityForTestOptions<Roles extends string = string> {\n /** Role that bypasses all checks (mirrors `CaslModule.forRoot`). */\n superuserRole?: Roles;\n\n /** Custom subject-type detection (mirrors `CaslModule.forRoot`). */\n detectSubjectType?: (subject: object) => string;\n}\n\n/**\n * Build a CASL ability directly from a permissions map and a user, without\n * booting a Nest application. Intended for unit-testing permission definitions\n * in projects that consume `@jperezmart/nest-casl`.\n *\n * @example\n * ```ts\n * const ability = buildAbilityForTest(permissions, { id: \"1\", roles: [\"author\"] });\n * expect(ability.can(\"update\", subject(\"Article\", { authorId: \"1\" }))).toBe(true);\n * ```\n */\nexport function buildAbilityForTest<\n Roles extends string = string,\n TUser extends AuthorizableUser<Roles> = AuthorizableUser<Roles>,\n TAbility extends AnyAbility = AnyAbility,\n>(\n permissions: Permissions<Roles, TUser, TAbility>,\n user: TUser,\n options: BuildAbilityForTestOptions<Roles> = {},\n): TAbility {\n const builder = new AbilityBuilder<MongoAbility>(createMongoAbility);\n const buildOptions = options.detectSubjectType\n ? { detectSubjectType: options.detectSubjectType }\n : undefined;\n\n // Mirror AbilityFactory: a missing / non-array `roles` means \"no roles\".\n const roles: string[] = Array.isArray(user.roles) ? user.roles : [];\n\n if (\n options.superuserRole !== undefined &&\n roles.includes(options.superuserRole)\n ) {\n builder.can('manage', 'all');\n return builder.build(buildOptions) as unknown as TAbility;\n }\n\n for (const role of roles) {\n const definition = (permissions as Record<string, unknown>)[role];\n if (definition === true) {\n builder.can('manage', 'all');\n } else if (typeof definition === 'function') {\n (definition as DefinePermissions<TUser, AnyAbility>)(\n user,\n builder as unknown as AbilityBuilder<AnyAbility>,\n );\n }\n }\n\n return builder.build(buildOptions) as unknown as TAbility;\n}\n"],"mappings":";;;;;;;;;;;;;;AA4BA,SAAgB,oBAKd,aACA,MACA,UAA6C,CAAC,GACpC;CACV,MAAM,UAAU,IAAIA,cAAAA,eAA6BC,cAAAA,kBAAkB;CACnE,MAAM,eAAe,QAAQ,oBACzB,EAAE,mBAAmB,QAAQ,kBAAkB,IAC/C,KAAA;CAGJ,MAAM,QAAkB,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;CAElE,IACE,QAAQ,kBAAkB,KAAA,KAC1B,MAAM,SAAS,QAAQ,aAAa,GACpC;EACA,QAAQ,IAAI,UAAU,KAAK;EAC3B,OAAO,QAAQ,MAAM,YAAY;CACnC;CAEA,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,aAAc,YAAwC;EAC5D,IAAI,eAAe,MACjB,QAAQ,IAAI,UAAU,KAAK;OACtB,IAAI,OAAO,eAAe,YAC/B,WACE,MACA,OACF;CAEJ;CAEA,OAAO,QAAQ,MAAM,YAAY;AACnC"}
1
+ {"version":3,"file":"index.cjs","names":["AbilityBuilder","createMongoAbility"],"sources":["../src/index.ts"],"sourcesContent":["import type { AnyAbility, MongoAbility } from '@casl/ability';\nimport { AbilityBuilder, createMongoAbility } from '@casl/ability';\nimport type {\n AuthorizableUser,\n DefineRolePermissions,\n RolePermissions,\n} from '@jperezmart/nest-casl';\n\n/** Extra options accepted by {@link buildAbilityForTest}. */\nexport interface BuildAbilityForTestOptions {\n /**\n * Role that bypasses all checks (mirrors `CaslModule.forRoot`). Any string:\n * the superuser role usually has no entry in the Role permissions.\n */\n superuserRole?: string;\n\n /** Custom subject-type detection (mirrors `CaslModule.forRoot`). */\n detectSubjectType?: (subject: object) => string;\n}\n\n/**\n * Build a CASL ability directly from Role permissions and a user, without\n * booting a Nest application. Intended for unit-testing permission definitions\n * in projects that consume `@jperezmart/nest-casl`. The user and ability types\n * are inferred from `permissions`; the user may carry roles the map does not\n * declare. Everyone permissions apply first, then the user's Roles.\n *\n * @example\n * ```ts\n * const ability = buildAbilityForTest(permissions, { id: \"1\", roles: [\"author\"] });\n * expect(ability.can(\"update\", subject(\"Article\", { authorId: \"1\" }))).toBe(true);\n * ```\n */\nexport function buildAbilityForTest<\n TUser extends AuthorizableUser = AuthorizableUser,\n TAbility extends AnyAbility = AnyAbility,\n>(\n permissions: RolePermissions<string, TUser, TAbility>,\n user: TUser,\n options: BuildAbilityForTestOptions = {},\n): TAbility {\n const builder = new AbilityBuilder<MongoAbility>(createMongoAbility);\n const buildOptions = options.detectSubjectType\n ? { detectSubjectType: options.detectSubjectType }\n : undefined;\n\n // Mirror AbilityFactory: a missing / non-array `roles` means \"no roles\"\n // (Everyone permissions still apply).\n const roles: readonly string[] = Array.isArray(user.roles) ? user.roles : [];\n\n if (\n options.superuserRole !== undefined &&\n roles.includes(options.superuserRole)\n ) {\n builder.can('manage', 'all');\n return builder.build(buildOptions) as unknown as TAbility;\n }\n\n const abilityBuilder = builder as unknown as AbilityBuilder<AnyAbility>;\n const everyone = (permissions as Record<string, unknown>)['everyone'];\n if (typeof everyone === 'function') {\n (everyone as DefineRolePermissions<TUser, AnyAbility>)(\n user,\n abilityBuilder,\n );\n }\n\n for (const role of roles) {\n // A foreign Role named `everyone` is not a Role: its rules are already in.\n if (role === 'everyone') continue;\n const definition = (permissions as Record<string, unknown>)[role];\n if (definition === true) {\n builder.can('manage', 'all');\n } else if (typeof definition === 'function') {\n (definition as DefineRolePermissions<TUser, AnyAbility>)(\n user,\n abilityBuilder,\n );\n }\n }\n\n return builder.build(buildOptions) as unknown as TAbility;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAiCA,SAAgB,oBAId,aACA,MACA,UAAsC,CAAC,GAC7B;CACV,MAAM,UAAU,IAAIA,cAAAA,eAA6BC,cAAAA,kBAAkB;CACnE,MAAM,eAAe,QAAQ,oBACzB,EAAE,mBAAmB,QAAQ,kBAAkB,IAC/C,KAAA;CAIJ,MAAM,QAA2B,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;CAE3E,IACE,QAAQ,kBAAkB,KAAA,KAC1B,MAAM,SAAS,QAAQ,aAAa,GACpC;EACA,QAAQ,IAAI,UAAU,KAAK;EAC3B,OAAO,QAAQ,MAAM,YAAY;CACnC;CAEA,MAAM,iBAAiB;CACvB,MAAM,WAAY,YAAwC;CAC1D,IAAI,OAAO,aAAa,YACtB,SACE,MACA,cACF;CAGF,KAAK,MAAM,QAAQ,OAAO;EAExB,IAAI,SAAS,YAAY;EACzB,MAAM,aAAc,YAAwC;EAC5D,IAAI,eAAe,MACjB,QAAQ,IAAI,UAAU,KAAK;OACtB,IAAI,OAAO,eAAe,YAC/B,WACE,MACA,cACF;CAEJ;CAEA,OAAO,QAAQ,MAAM,YAAY;AACnC"}
package/dist/index.d.cts CHANGED
@@ -1,18 +1,23 @@
1
1
  import { AnyAbility } from "@casl/ability";
2
- import { AuthorizableUser, Permissions } from "@jperezmart/nest-casl";
2
+ import { AuthorizableUser, RolePermissions } from "@jperezmart/nest-casl";
3
3
 
4
4
  //#region src/index.d.ts
5
5
  /** Extra options accepted by {@link buildAbilityForTest}. */
6
- interface BuildAbilityForTestOptions<Roles extends string = string> {
7
- /** Role that bypasses all checks (mirrors `CaslModule.forRoot`). */
8
- superuserRole?: Roles;
6
+ interface BuildAbilityForTestOptions {
7
+ /**
8
+ * Role that bypasses all checks (mirrors `CaslModule.forRoot`). Any string:
9
+ * the superuser role usually has no entry in the Role permissions.
10
+ */
11
+ superuserRole?: string;
9
12
  /** Custom subject-type detection (mirrors `CaslModule.forRoot`). */
10
13
  detectSubjectType?: (subject: object) => string;
11
14
  }
12
15
  /**
13
- * Build a CASL ability directly from a permissions map and a user, without
16
+ * Build a CASL ability directly from Role permissions and a user, without
14
17
  * booting a Nest application. Intended for unit-testing permission definitions
15
- * in projects that consume `@jperezmart/nest-casl`.
18
+ * in projects that consume `@jperezmart/nest-casl`. The user and ability types
19
+ * are inferred from `permissions`; the user may carry roles the map does not
20
+ * declare. Everyone permissions apply first, then the user's Roles.
16
21
  *
17
22
  * @example
18
23
  * ```ts
@@ -20,7 +25,7 @@ interface BuildAbilityForTestOptions<Roles extends string = string> {
20
25
  * expect(ability.can("update", subject("Article", { authorId: "1" }))).toBe(true);
21
26
  * ```
22
27
  */
23
- declare function buildAbilityForTest<Roles extends string = string, TUser extends AuthorizableUser<Roles> = AuthorizableUser<Roles>, TAbility extends AnyAbility = AnyAbility>(permissions: Permissions<Roles, TUser, TAbility>, user: TUser, options?: BuildAbilityForTestOptions<Roles>): TAbility;
28
+ declare function buildAbilityForTest<TUser extends AuthorizableUser = AuthorizableUser, TAbility extends AnyAbility = AnyAbility>(permissions: RolePermissions<string, TUser, TAbility>, user: TUser, options?: BuildAbilityForTestOptions): TAbility;
24
29
  //#endregion
25
30
  export { BuildAbilityForTestOptions, buildAbilityForTest };
26
31
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;UASiB,0BAAA;EAAA;EAEf,aAAA,GAAgB,KAAK;EAFoB;EAKzC,iBAAA,IAAqB,OAAA;AAAA;;;;;;AAAe;AActC;;;;;iBAAgB,mBAAA,8CAEA,gBAAA,CAAiB,KAAA,IAAS,gBAAA,CAAiB,KAAA,oBACxC,UAAA,GAAa,UAAA,EAE9B,WAAA,EAAa,WAAA,CAAY,KAAA,EAAO,KAAA,EAAO,QAAA,GACvC,IAAA,EAAM,KAAA,EACN,OAAA,GAAS,0BAAA,CAA2B,KAAA,IACnC,QAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;UASiB,0BAAA;EAAA;;;;EAKf,aAAA;EAGA;EAAA,iBAAA,IAAqB,OAAA;AAAA;AAAe;AAgBtC;;;;;;;;;;;;AAhBsC,iBAgBtB,mBAAA,eACA,gBAAA,GAAmB,gBAAA,mBAChB,UAAA,GAAa,UAAA,EAE9B,WAAA,EAAa,eAAA,SAAwB,KAAA,EAAO,QAAA,GAC5C,IAAA,EAAM,KAAA,EACN,OAAA,GAAS,0BAAA,GACR,QAAA"}
package/dist/index.d.ts CHANGED
@@ -1,18 +1,23 @@
1
1
  import { AnyAbility } from "@casl/ability";
2
- import { AuthorizableUser, Permissions } from "@jperezmart/nest-casl";
2
+ import { AuthorizableUser, RolePermissions } from "@jperezmart/nest-casl";
3
3
 
4
4
  //#region src/index.d.ts
5
5
  /** Extra options accepted by {@link buildAbilityForTest}. */
6
- interface BuildAbilityForTestOptions<Roles extends string = string> {
7
- /** Role that bypasses all checks (mirrors `CaslModule.forRoot`). */
8
- superuserRole?: Roles;
6
+ interface BuildAbilityForTestOptions {
7
+ /**
8
+ * Role that bypasses all checks (mirrors `CaslModule.forRoot`). Any string:
9
+ * the superuser role usually has no entry in the Role permissions.
10
+ */
11
+ superuserRole?: string;
9
12
  /** Custom subject-type detection (mirrors `CaslModule.forRoot`). */
10
13
  detectSubjectType?: (subject: object) => string;
11
14
  }
12
15
  /**
13
- * Build a CASL ability directly from a permissions map and a user, without
16
+ * Build a CASL ability directly from Role permissions and a user, without
14
17
  * booting a Nest application. Intended for unit-testing permission definitions
15
- * in projects that consume `@jperezmart/nest-casl`.
18
+ * in projects that consume `@jperezmart/nest-casl`. The user and ability types
19
+ * are inferred from `permissions`; the user may carry roles the map does not
20
+ * declare. Everyone permissions apply first, then the user's Roles.
16
21
  *
17
22
  * @example
18
23
  * ```ts
@@ -20,7 +25,7 @@ interface BuildAbilityForTestOptions<Roles extends string = string> {
20
25
  * expect(ability.can("update", subject("Article", { authorId: "1" }))).toBe(true);
21
26
  * ```
22
27
  */
23
- declare function buildAbilityForTest<Roles extends string = string, TUser extends AuthorizableUser<Roles> = AuthorizableUser<Roles>, TAbility extends AnyAbility = AnyAbility>(permissions: Permissions<Roles, TUser, TAbility>, user: TUser, options?: BuildAbilityForTestOptions<Roles>): TAbility;
28
+ declare function buildAbilityForTest<TUser extends AuthorizableUser = AuthorizableUser, TAbility extends AnyAbility = AnyAbility>(permissions: RolePermissions<string, TUser, TAbility>, user: TUser, options?: BuildAbilityForTestOptions): TAbility;
24
29
  //#endregion
25
30
  export { BuildAbilityForTestOptions, buildAbilityForTest };
26
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;UASiB,0BAAA;EAAA;EAEf,aAAA,GAAgB,KAAK;EAFoB;EAKzC,iBAAA,IAAqB,OAAA;AAAA;;;;;;AAAe;AActC;;;;;iBAAgB,mBAAA,8CAEA,gBAAA,CAAiB,KAAA,IAAS,gBAAA,CAAiB,KAAA,oBACxC,UAAA,GAAa,UAAA,EAE9B,WAAA,EAAa,WAAA,CAAY,KAAA,EAAO,KAAA,EAAO,QAAA,GACvC,IAAA,EAAM,KAAA,EACN,OAAA,GAAS,0BAAA,CAA2B,KAAA,IACnC,QAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;UASiB,0BAAA;EAAA;;;;EAKf,aAAA;EAGA;EAAA,iBAAA,IAAqB,OAAA;AAAA;AAAe;AAgBtC;;;;;;;;;;;;AAhBsC,iBAgBtB,mBAAA,eACA,gBAAA,GAAmB,gBAAA,mBAChB,UAAA,GAAa,UAAA,EAE9B,WAAA,EAAa,eAAA,SAAwB,KAAA,EAAO,QAAA,GAC5C,IAAA,EAAM,KAAA,EACN,OAAA,GAAS,0BAAA,GACR,QAAA"}
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { AbilityBuilder, createMongoAbility } from "@casl/ability";
2
2
  //#region src/index.ts
3
3
  /**
4
- * Build a CASL ability directly from a permissions map and a user, without
4
+ * Build a CASL ability directly from Role permissions and a user, without
5
5
  * booting a Nest application. Intended for unit-testing permission definitions
6
- * in projects that consume `@jperezmart/nest-casl`.
6
+ * in projects that consume `@jperezmart/nest-casl`. The user and ability types
7
+ * are inferred from `permissions`; the user may carry roles the map does not
8
+ * declare. Everyone permissions apply first, then the user's Roles.
7
9
  *
8
10
  * @example
9
11
  * ```ts
@@ -19,10 +21,14 @@ function buildAbilityForTest(permissions, user, options = {}) {
19
21
  builder.can("manage", "all");
20
22
  return builder.build(buildOptions);
21
23
  }
24
+ const abilityBuilder = builder;
25
+ const everyone = permissions["everyone"];
26
+ if (typeof everyone === "function") everyone(user, abilityBuilder);
22
27
  for (const role of roles) {
28
+ if (role === "everyone") continue;
23
29
  const definition = permissions[role];
24
30
  if (definition === true) builder.can("manage", "all");
25
- else if (typeof definition === "function") definition(user, builder);
31
+ else if (typeof definition === "function") definition(user, abilityBuilder);
26
32
  }
27
33
  return builder.build(buildOptions);
28
34
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { AnyAbility, MongoAbility } from '@casl/ability';\nimport { AbilityBuilder, createMongoAbility } from '@casl/ability';\nimport type {\n AuthorizableUser,\n DefinePermissions,\n Permissions,\n} from '@jperezmart/nest-casl';\n\n/** Extra options accepted by {@link buildAbilityForTest}. */\nexport interface BuildAbilityForTestOptions<Roles extends string = string> {\n /** Role that bypasses all checks (mirrors `CaslModule.forRoot`). */\n superuserRole?: Roles;\n\n /** Custom subject-type detection (mirrors `CaslModule.forRoot`). */\n detectSubjectType?: (subject: object) => string;\n}\n\n/**\n * Build a CASL ability directly from a permissions map and a user, without\n * booting a Nest application. Intended for unit-testing permission definitions\n * in projects that consume `@jperezmart/nest-casl`.\n *\n * @example\n * ```ts\n * const ability = buildAbilityForTest(permissions, { id: \"1\", roles: [\"author\"] });\n * expect(ability.can(\"update\", subject(\"Article\", { authorId: \"1\" }))).toBe(true);\n * ```\n */\nexport function buildAbilityForTest<\n Roles extends string = string,\n TUser extends AuthorizableUser<Roles> = AuthorizableUser<Roles>,\n TAbility extends AnyAbility = AnyAbility,\n>(\n permissions: Permissions<Roles, TUser, TAbility>,\n user: TUser,\n options: BuildAbilityForTestOptions<Roles> = {},\n): TAbility {\n const builder = new AbilityBuilder<MongoAbility>(createMongoAbility);\n const buildOptions = options.detectSubjectType\n ? { detectSubjectType: options.detectSubjectType }\n : undefined;\n\n // Mirror AbilityFactory: a missing / non-array `roles` means \"no roles\".\n const roles: string[] = Array.isArray(user.roles) ? user.roles : [];\n\n if (\n options.superuserRole !== undefined &&\n roles.includes(options.superuserRole)\n ) {\n builder.can('manage', 'all');\n return builder.build(buildOptions) as unknown as TAbility;\n }\n\n for (const role of roles) {\n const definition = (permissions as Record<string, unknown>)[role];\n if (definition === true) {\n builder.can('manage', 'all');\n } else if (typeof definition === 'function') {\n (definition as DefinePermissions<TUser, AnyAbility>)(\n user,\n builder as unknown as AbilityBuilder<AnyAbility>,\n );\n }\n }\n\n return builder.build(buildOptions) as unknown as TAbility;\n}\n"],"mappings":";;;;;;;;;;;;;AA4BA,SAAgB,oBAKd,aACA,MACA,UAA6C,CAAC,GACpC;CACV,MAAM,UAAU,IAAI,eAA6B,kBAAkB;CACnE,MAAM,eAAe,QAAQ,oBACzB,EAAE,mBAAmB,QAAQ,kBAAkB,IAC/C,KAAA;CAGJ,MAAM,QAAkB,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;CAElE,IACE,QAAQ,kBAAkB,KAAA,KAC1B,MAAM,SAAS,QAAQ,aAAa,GACpC;EACA,QAAQ,IAAI,UAAU,KAAK;EAC3B,OAAO,QAAQ,MAAM,YAAY;CACnC;CAEA,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,aAAc,YAAwC;EAC5D,IAAI,eAAe,MACjB,QAAQ,IAAI,UAAU,KAAK;OACtB,IAAI,OAAO,eAAe,YAC/B,WACE,MACA,OACF;CAEJ;CAEA,OAAO,QAAQ,MAAM,YAAY;AACnC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { AnyAbility, MongoAbility } from '@casl/ability';\nimport { AbilityBuilder, createMongoAbility } from '@casl/ability';\nimport type {\n AuthorizableUser,\n DefineRolePermissions,\n RolePermissions,\n} from '@jperezmart/nest-casl';\n\n/** Extra options accepted by {@link buildAbilityForTest}. */\nexport interface BuildAbilityForTestOptions {\n /**\n * Role that bypasses all checks (mirrors `CaslModule.forRoot`). Any string:\n * the superuser role usually has no entry in the Role permissions.\n */\n superuserRole?: string;\n\n /** Custom subject-type detection (mirrors `CaslModule.forRoot`). */\n detectSubjectType?: (subject: object) => string;\n}\n\n/**\n * Build a CASL ability directly from Role permissions and a user, without\n * booting a Nest application. Intended for unit-testing permission definitions\n * in projects that consume `@jperezmart/nest-casl`. The user and ability types\n * are inferred from `permissions`; the user may carry roles the map does not\n * declare. Everyone permissions apply first, then the user's Roles.\n *\n * @example\n * ```ts\n * const ability = buildAbilityForTest(permissions, { id: \"1\", roles: [\"author\"] });\n * expect(ability.can(\"update\", subject(\"Article\", { authorId: \"1\" }))).toBe(true);\n * ```\n */\nexport function buildAbilityForTest<\n TUser extends AuthorizableUser = AuthorizableUser,\n TAbility extends AnyAbility = AnyAbility,\n>(\n permissions: RolePermissions<string, TUser, TAbility>,\n user: TUser,\n options: BuildAbilityForTestOptions = {},\n): TAbility {\n const builder = new AbilityBuilder<MongoAbility>(createMongoAbility);\n const buildOptions = options.detectSubjectType\n ? { detectSubjectType: options.detectSubjectType }\n : undefined;\n\n // Mirror AbilityFactory: a missing / non-array `roles` means \"no roles\"\n // (Everyone permissions still apply).\n const roles: readonly string[] = Array.isArray(user.roles) ? user.roles : [];\n\n if (\n options.superuserRole !== undefined &&\n roles.includes(options.superuserRole)\n ) {\n builder.can('manage', 'all');\n return builder.build(buildOptions) as unknown as TAbility;\n }\n\n const abilityBuilder = builder as unknown as AbilityBuilder<AnyAbility>;\n const everyone = (permissions as Record<string, unknown>)['everyone'];\n if (typeof everyone === 'function') {\n (everyone as DefineRolePermissions<TUser, AnyAbility>)(\n user,\n abilityBuilder,\n );\n }\n\n for (const role of roles) {\n // A foreign Role named `everyone` is not a Role: its rules are already in.\n if (role === 'everyone') continue;\n const definition = (permissions as Record<string, unknown>)[role];\n if (definition === true) {\n builder.can('manage', 'all');\n } else if (typeof definition === 'function') {\n (definition as DefineRolePermissions<TUser, AnyAbility>)(\n user,\n abilityBuilder,\n );\n }\n }\n\n return builder.build(buildOptions) as unknown as TAbility;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiCA,SAAgB,oBAId,aACA,MACA,UAAsC,CAAC,GAC7B;CACV,MAAM,UAAU,IAAI,eAA6B,kBAAkB;CACnE,MAAM,eAAe,QAAQ,oBACzB,EAAE,mBAAmB,QAAQ,kBAAkB,IAC/C,KAAA;CAIJ,MAAM,QAA2B,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;CAE3E,IACE,QAAQ,kBAAkB,KAAA,KAC1B,MAAM,SAAS,QAAQ,aAAa,GACpC;EACA,QAAQ,IAAI,UAAU,KAAK;EAC3B,OAAO,QAAQ,MAAM,YAAY;CACnC;CAEA,MAAM,iBAAiB;CACvB,MAAM,WAAY,YAAwC;CAC1D,IAAI,OAAO,aAAa,YACtB,SACE,MACA,cACF;CAGF,KAAK,MAAM,QAAQ,OAAO;EAExB,IAAI,SAAS,YAAY;EACzB,MAAM,aAAc,YAAwC;EAC5D,IAAI,eAAe,MACjB,QAAQ,IAAI,UAAU,KAAK;OACtB,IAAI,OAAO,eAAe,YAC/B,WACE,MACA,cACF;CAEJ;CAEA,OAAO,QAAQ,MAAM,YAAY;AACnC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jperezmart/nest-casl-testing",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "Testing utilities for @jperezmart/nest-casl — build and assert abilities without booting Nest.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,7 +35,7 @@
35
35
  "devDependencies": {
36
36
  "@casl/ability": "^7.0.0",
37
37
  "@jperezmart/eslint-config": "^1.0.0",
38
- "@jperezmart/nest-casl": "0.1.2",
38
+ "@jperezmart/nest-casl": "0.3.0",
39
39
  "@jperezmart/typescript-config": "^1.0.0",
40
40
  "eslint": "^10.3.0",
41
41
  "tsdown": "^0.22.2",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@casl/ability": "^7.0.0",
47
- "@jperezmart/nest-casl": "^0.1.2"
47
+ "@jperezmart/nest-casl": "^0.3.0"
48
48
  },
49
49
  "publishConfig": {
50
50
  "access": "public"