@dudousxd/nestjs-authz-testing 0.1.1 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @dudousxd/nestjs-authz-testing
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#67](https://github.com/DavideCarvalho/nestjs-authz/pull/67) [`32e967f`](https://github.com/DavideCarvalho/nestjs-authz/commit/32e967ffbcd68a97e8effc77af9c16bbbafdc918) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `buildGate` / `buildBoundGate` are async and compile a Nest testing module with the official
8
+ `AuthorizationModule` and `AuthzModule`, so decisions run through `AuthorizationService`.
9
+ `policies` takes classes (or providers) instead of instances; the `after` option is gone. New:
10
+ `createAuthzTestingModule()`, official-style `expectCan(gate, user, Policy, 'ability', ...args)`,
11
+ and `requirementsOf(Controller, 'method')`. Peers on `@nestjs/authorization`,
12
+ `@nestjs/common`/`core`/`testing` 12.
13
+
14
+ ### Patch Changes
15
+
16
+ - [#67](https://github.com/DavideCarvalho/nestjs-authz/pull/67) [`32e967f`](https://github.com/DavideCarvalho/nestjs-authz/commit/32e967ffbcd68a97e8effc77af9c16bbbafdc918) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Fix packages that could be published without files their `exports` point to. `tsc` builds no longer use a `.tsbuildinfo` that can outlive `dist/` and skip emitting files. A new `check:exports` step (also `prepack`) fails the release if any `exports`/`main`/`types` path is missing.
17
+
3
18
  ## 0.1.1
4
19
 
5
20
  ### Patch Changes
package/README.md CHANGED
@@ -1,34 +1,54 @@
1
1
  # `@dudousxd/nestjs-authz-testing`
2
2
 
3
- Test helpers for [`@dudousxd/nestjs-authz`](../core). Build a real `Gate`/`BoundGate`
4
- from fixture users, policies, gates, and an in-memory permission/role provider — no
5
- Nest module, no database — then assert verdicts with `expectCan` / `expectCannot`.
3
+ Test helpers for [`@dudousxd/nestjs-authz`](../core) on
4
+ [`@nestjs/authorization`](https://docs.nestjs.com/security/authorization). `buildGate` compiles
5
+ a real Nest testing module with the official `AuthorizationModule` and `AuthzModule`, so every
6
+ policy decision goes through the official `AuthorizationService`, exactly as in the app.
6
7
 
7
8
  ```ts
8
9
  import 'reflect-metadata';
9
- import { Policy } from '@dudousxd/nestjs-authz';
10
+ import { Policy } from '@nestjs/authorization';
11
+ import { PolicyFor } from '@dudousxd/nestjs-authz';
10
12
  import { buildGate, expectCan, expectCannot, FakePermissionProvider } from '@dudousxd/nestjs-authz-testing';
11
13
 
12
- @Policy(Post)
14
+ @Policy()
15
+ @PolicyFor(Post)
13
16
  class PostPolicy {
14
- update(user, post) { return user?.id === post.authorId; }
17
+ update(user: User | null, post: Post) { return user?.id === post.authorId; }
15
18
  }
16
19
 
17
- const gate = buildGate({ policies: [new PostPolicy()] });
18
- await expectCan(gate, { id: 1 }, 'update', new Post(10, 1));
20
+ const gate = await buildGate({ policies: [PostPolicy] });
21
+ await expectCan(gate, { id: 1 }, PostPolicy, 'update', new Post(10, 1)); // official style
22
+ await expectCan(gate, { id: 1 }, 'update', new Post(10, 1)); // via @PolicyFor(Post)
19
23
  await expectCannot(gate, { id: 2 }, 'update', new Post(10, 1));
20
24
 
21
- // RBAC seam, zero DB — wildcard grants work via the core matcher:
22
- const gate2 = buildGate({ permissionProvider: new FakePermissionProvider({ '1': ['posts.*'] }) });
25
+ // RBAC seam, zero DB; wildcard grants work through the core matcher:
26
+ const gate2 = await buildGate({ permissionProvider: new FakePermissionProvider({ '1': ['posts.*'] }) });
23
27
  await expectCan(gate2, { id: 1 }, 'posts.update');
24
28
  ```
25
29
 
30
+ Assert what a route requires without booting HTTP:
31
+
32
+ ```ts
33
+ expect(requirementsOf(PostController, 'publish')).toEqual([
34
+ { policy: PermissionPolicy, ability: 'has', staticArgs: ['posts.publish'], dynamic: false },
35
+ ]);
36
+ ```
37
+
26
38
  ## API
27
39
 
28
- - `buildGate(options?)` → `Gate` from `{ policies, gates, superAdmin, after, resolveRoles, permissionProvider, roleProvider }`.
29
- - `buildBoundGate(user, options?)` → `BoundGate` bound to a fixture user.
30
- - `expectCan(gate, user, ability, resource?)` / `expectCannot(...)` — assert the verdict
31
- (accept a `Gate` + user, or a `BoundGate` with `undefined` user). `expectCannot`
32
- treats an unresolved ability as "cannot".
40
+ - `buildGate(options?)` → `Promise<Gate>`; `createAuthzTestingModule(options?)` →
41
+ `{ module, gate }` (close `module` when done). Options: `{ policies, providers, controllers,
42
+ gates, superAdmin, resolveRoles, resourceResolver, getUser, permissionProvider, roleProvider }`.
43
+ `policies` are classes (or providers, e.g. `{ provide: PostPolicy, useValue }`).
44
+ - `buildBoundGate(user, options?)` → `Promise<BoundGate>`.
45
+ - `expectCan(gate, user, ability, resource?)` / `expectCan(gate, user, Policy, ability, ...args)`,
46
+ and `expectCannot(...)`. Accept a `Gate` + user, or a `BoundGate` with `undefined` user.
47
+ `expectCannot` treats an unresolved ability as "cannot".
48
+ - `requirementsOf(Controller, 'method')` — the official `@Can()` requirements the guard
49
+ enforces there, class-level first, with the arguments of static resolvers.
33
50
  - `FakePermissionProvider` / `FakeRoleProvider` — in-memory RBAC seams keyed by user id.
34
51
  - `AuthzAssertionError` — thrown on assertion failure.
52
+
53
+ Migrating from 0.1: `buildGate` / `buildBoundGate` are now async, take policy classes instead of
54
+ instances, and the `after` option is gone.
package/dist/index.d.ts CHANGED
@@ -1,84 +1,99 @@
1
1
  /**
2
- * Test helpers for `@dudousxd/nestjs-authz`.
2
+ * Test helpers for `@dudousxd/nestjs-authz` on `@nestjs/authorization`.
3
3
  *
4
- * Build a real {@link Gate} (no Nest module, no DB) from fixture policies, ad-hoc
5
- * gates, hooks, and an optional in-memory permission/role provider — then assert
6
- * verdicts with {@link expectCan}/{@link expectCannot}. The helpers exercise the
7
- * SAME resolution path the production Gate uses, so a policy that passes here passes
8
- * in the app.
4
+ * {@link buildGate} compiles a real Nest testing module with the official
5
+ * `AuthorizationModule` and `AuthzModule`, so every policy decision goes through the
6
+ * official `AuthorizationService`, exactly as in the app. Assert with
7
+ * {@link expectCan} / {@link expectCannot}, and read what a route requires with
8
+ * {@link requirementsOf}.
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * const gate = buildGate({ policies: [new PostPolicy()] });
13
- * await expectCan(gate, { id: 1 }, 'update', new Post(1, 1));
14
- * await expectCannot(gate, { id: 2 }, 'update', new Post(1, 1));
12
+ * const gate = await buildGate({ policies: [PostPolicy] });
13
+ * await expectCan(gate, { id: 1 }, 'update', new Post(10, 1)); // linked with @PolicyFor(Post)
14
+ * await expectCan(gate, { id: 1 }, PostPolicy, 'update', new Post(10, 1)); // official style
15
15
  * ```
16
16
  *
17
17
  * @module
18
18
  */
19
- import { type AfterHook, BoundGate, Gate, type GateFn, type PermissionProvider, type PolicyInstance, type Resource, type RoleProvider, type SuperAdminHook, type User } from '@dudousxd/nestjs-authz';
20
- export { FakePermissionProvider, FakeRoleProvider, } from './fakes.js';
19
+ import { BoundGate, Gate, type GateFn, type PermissionProvider, type Resource, type ResourceResolver, type RoleProvider, type SuperAdminHook, type User } from '@dudousxd/nestjs-authz';
20
+ import type { ExecutionContext, Provider, Type } from '@nestjs/common';
21
+ import { type TestingModule } from '@nestjs/testing';
22
+ export { FakePermissionProvider, FakeRoleProvider } from './fakes.js';
21
23
  export type { FakePermissionGrants } from './fakes.js';
22
24
  /** Fixture configuration for {@link buildGate}. Every field is optional. */
23
25
  export interface BuildGateOptions {
24
- /**
25
- * Policy INSTANCES to register (e.g. `new PostPolicy()`). Unlike the Nest module,
26
- * the test helper does not run DI — pass already-constructed instances. Each MUST be
27
- * `@Policy(Resource)`-decorated (the registry resolves by resource constructor and
28
- * throws on an undecorated class, same as production).
29
- */
30
- policies?: PolicyInstance[];
31
- /** Ad-hoc, model-less gates keyed by ability name (same as `gate.define(name, fn)`). */
26
+ /** Official `@Policy()` classes (or providers, e.g. `{ provide: PostPolicy, useValue }`). */
27
+ policies?: Provider[];
28
+ /** More providers the policies depend on. */
29
+ providers?: Provider[];
30
+ /** Controllers, when the test also boots routes. */
31
+ controllers?: Type<unknown>[];
32
+ /** Ad-hoc gates keyed by ability name (`gate.define(name, fn)`). */
32
33
  gates?: Record<string, GateFn>;
33
- /** Global super-admin before-hook (Laravel `Gate::before`). */
34
+ /** The global super-admin hook. */
34
35
  superAdmin?: SuperAdminHook;
35
- /** Global `after` hook (Laravel `Gate::after`). */
36
- after?: AfterHook;
37
- /** Override how a user's roles are derived (default reads `user.roles`/`user.role`). */
36
+ /** How roles are read off the user (default `user.roles` / `user.role`). */
38
37
  resolveRoles?: (user: User) => string[] | undefined | Promise<string[] | undefined>;
39
- /**
40
- * An in-memory {@link PermissionProvider} (the RBAC seam) — grants named abilities
41
- * when the fixture user holds the permission. Use {@link FakePermissionProvider} for
42
- * a zero-DB stand-in.
43
- */
38
+ /** The resolver `byId()` uses. */
39
+ resourceResolver?: ResourceResolver;
40
+ /** The official `getUser`, for routes. */
41
+ getUser?: (context: ExecutionContext) => unknown;
42
+ /** An in-memory permission seam. See {@link FakePermissionProvider}. */
44
43
  permissionProvider?: PermissionProvider;
45
- /** An in-memory {@link RoleProvider} for coarse role checks. Use {@link FakeRoleProvider}. */
44
+ /** An in-memory role seam. See {@link FakeRoleProvider}. */
46
45
  roleProvider?: RoleProvider;
47
46
  }
47
+ /** The compiled testing module, initialized, and its {@link Gate}. */
48
+ export interface AuthzTestingModule {
49
+ module: TestingModule;
50
+ gate: Gate;
51
+ }
48
52
  /**
49
- * Build a real {@link Gate} from fixtures — no Nest container, no database. The
50
- * returned Gate behaves exactly like the production one (same hooks, same RBAC seam,
51
- * same resolution order); only the wiring is in-memory.
52
- */
53
- export declare function buildGate(options?: BuildGateOptions): Gate;
54
- /**
55
- * Build a {@link BoundGate} for a fixture `user` directly — sugar over
56
- * `buildGate(options).forUser(user)` for tests that focus on a single principal.
57
- */
58
- export declare function buildBoundGate(user: User, options?: BuildGateOptions): BoundGate;
59
- /**
60
- * Accept either a {@link Gate} (then `user` is required) or an already-bound
61
- * {@link BoundGate}. Lets the assertion helpers serve both styles.
53
+ * Compile and initialize a Nest testing module with the official `AuthorizationModule`,
54
+ * `AuthzModule` and the fixtures. Close `module` when done (or let the test process end).
62
55
  */
56
+ export declare function createAuthzTestingModule(options?: BuildGateOptions): Promise<AuthzTestingModule>;
57
+ /** A real {@link Gate} from fixtures. See {@link createAuthzTestingModule}. */
58
+ export declare function buildGate(options?: BuildGateOptions): Promise<Gate>;
59
+ /** A {@link BoundGate} for a fixture `user`: `(await buildGate(options)).forUser(user)`. */
60
+ export declare function buildBoundGate(user: User, options?: BuildGateOptions): Promise<BoundGate>;
61
+ /** A {@link Gate} (then `user` is required) or an already-bound {@link BoundGate}. */
63
62
  export type GateLike = Gate | BoundGate;
64
63
  /** A thrown assertion failure from {@link expectCan}/{@link expectCannot}. */
65
64
  export declare class AuthzAssertionError extends Error {
66
65
  constructor(message: string);
67
66
  }
67
+ /** What a check names: a string ability (+ resource), or an official policy + ability (+ args). */
68
+ type Check = [ability: string, resource?: Resource] | [policy: Type<unknown>, ability: string, ...args: unknown[]];
68
69
  /**
69
- * Assert the user CAN perform `ability` (optionally on `resource`). Resolves when
70
- * allowed; throws {@link AuthzAssertionError} when denied. Works with either a
71
- * {@link Gate} (+ `user`) or a {@link BoundGate} (pass `undefined` for `user`).
72
- *
73
- * @example await expectCan(gate, { id: 1 }, 'update', post);
74
- * @example await expectCan(boundGate, undefined, 'create');
70
+ * Assert the user CAN: `expectCan(gate, user, 'update', post)` or
71
+ * `expectCan(gate, user, PostPolicy, 'update', post)`. Throws {@link AuthzAssertionError}.
75
72
  */
76
- export declare function expectCan(gate: GateLike, user: User, ability: string, resource?: Resource): Promise<void>;
73
+ export declare function expectCan(gate: GateLike, user: User, ...check: Check): Promise<void>;
77
74
  /**
78
- * Assert the user CANNOT perform `ability` (optionally on `resource`). Resolves when
79
- * denied; throws {@link AuthzAssertionError} when allowed. An unresolved ability
80
- * (no policy/gate) counts as "cannot" — the deny-by-default outcome a caller wants
81
- * to assert — so this never leaks the underlying `AbilityNotResolvedException`.
75
+ * Assert the user CANNOT. An unresolved ability (no policy, gate or permission answers
76
+ * it) counts as "cannot", the deny-by-default outcome.
77
+ */
78
+ export declare function expectCannot(gate: GateLike, user: User, ...check: Check): Promise<void>;
79
+ /** One official `@Can()` requirement of a route, with its static arguments when it has some. */
80
+ export interface RouteRequirement {
81
+ policy: Type<unknown>;
82
+ ability: string;
83
+ /** The arguments of a static resolver (`roles('admin')`, `permission(...)`), else `undefined`. */
84
+ staticArgs: readonly unknown[] | undefined;
85
+ /** True when the resolver depends on the call (`byId(Post)`, a custom function). */
86
+ dynamic: boolean;
87
+ }
88
+ /**
89
+ * What the official guard requires on `controller.method`, class-level first. Lets a test
90
+ * assert a route's protection without booting HTTP:
91
+ *
92
+ * ```ts
93
+ * expect(requirementsOf(PostController, 'publish')).toEqual([
94
+ * { policy: PermissionPolicy, ability: 'has', staticArgs: ['posts.publish'], dynamic: false },
95
+ * ]);
96
+ * ```
82
97
  */
83
- export declare function expectCannot(gate: GateLike, user: User, ability: string, resource?: Resource): Promise<void>;
98
+ export declare function requirementsOf(controller: Type<unknown>, method: string): RouteRequirement[];
84
99
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,KAAK,SAAS,EACd,SAAS,EACT,IAAI,EACJ,KAAK,MAAM,EACX,KAAK,kBAAkB,EACvB,KAAK,cAAc,EAEnB,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,IAAI,EACV,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,wFAAwF;IACxF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,+DAA+D;IAC/D,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,mDAAmD;IACnD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,wFAAwF;IACxF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,EAAE,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACpF;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,8FAA8F;IAC9F,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,IAAI,CAwB9D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAEpF;AAED;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC;AAMxC,8EAA8E;AAC9E,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,IAAI,CAAC,CAaf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,50 +1,63 @@
1
1
  /**
2
- * Test helpers for `@dudousxd/nestjs-authz`.
2
+ * Test helpers for `@dudousxd/nestjs-authz` on `@nestjs/authorization`.
3
3
  *
4
- * Build a real {@link Gate} (no Nest module, no DB) from fixture policies, ad-hoc
5
- * gates, hooks, and an optional in-memory permission/role provider — then assert
6
- * verdicts with {@link expectCan}/{@link expectCannot}. The helpers exercise the
7
- * SAME resolution path the production Gate uses, so a policy that passes here passes
8
- * in the app.
4
+ * {@link buildGate} compiles a real Nest testing module with the official
5
+ * `AuthorizationModule` and `AuthzModule`, so every policy decision goes through the
6
+ * official `AuthorizationService`, exactly as in the app. Assert with
7
+ * {@link expectCan} / {@link expectCannot}, and read what a route requires with
8
+ * {@link requirementsOf}.
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * const gate = buildGate({ policies: [new PostPolicy()] });
13
- * await expectCan(gate, { id: 1 }, 'update', new Post(1, 1));
14
- * await expectCannot(gate, { id: 2 }, 'update', new Post(1, 1));
12
+ * const gate = await buildGate({ policies: [PostPolicy] });
13
+ * await expectCan(gate, { id: 1 }, 'update', new Post(10, 1)); // linked with @PolicyFor(Post)
14
+ * await expectCan(gate, { id: 1 }, PostPolicy, 'update', new Post(10, 1)); // official style
15
15
  * ```
16
16
  *
17
17
  * @module
18
18
  */
19
- import { BoundGate, Gate, PolicyRegistry, } from '@dudousxd/nestjs-authz';
20
- export { FakePermissionProvider, FakeRoleProvider, } from './fakes.js';
19
+ import { AuthzModule, BoundGate, Gate, PERMISSION_PROVIDER, ROLE_PROVIDER, getStaticCanArgs, routeRequirements, } from '@dudousxd/nestjs-authz';
20
+ import { AuthorizationModule } from '@nestjs/authorization';
21
+ import { Test } from '@nestjs/testing';
22
+ export { FakePermissionProvider, FakeRoleProvider } from './fakes.js';
21
23
  /**
22
- * Build a real {@link Gate} from fixtures — no Nest container, no database. The
23
- * returned Gate behaves exactly like the production one (same hooks, same RBAC seam,
24
- * same resolution order); only the wiring is in-memory.
24
+ * Compile and initialize a Nest testing module with the official `AuthorizationModule`,
25
+ * `AuthzModule` and the fixtures. Close `module` when done (or let the test process end).
25
26
  */
26
- export function buildGate(options = {}) {
27
- const registry = new PolicyRegistry();
28
- for (const policy of options.policies ?? [])
29
- registry.register(policy);
30
- const moduleOptions = {
31
- ...(options.superAdmin ? { superAdmin: options.superAdmin } : {}),
32
- ...(options.after ? { after: options.after } : {}),
33
- ...(options.resolveRoles ? { resolveRoles: options.resolveRoles } : {}),
34
- };
35
- // Ctor: (policies, options, context?, moduleRef?, permissionProvider?, roleProvider?).
36
- const gate = new Gate(registry, moduleOptions, undefined, undefined, options.permissionProvider, options.roleProvider);
37
- for (const [ability, fn] of Object.entries(options.gates ?? {})) {
27
+ export async function createAuthzTestingModule(options = {}) {
28
+ const module = await Test.createTestingModule({
29
+ imports: [
30
+ AuthorizationModule.forRoot(options.getUser ? { getUser: options.getUser } : {}),
31
+ AuthzModule.forRoot({
32
+ ...(options.superAdmin ? { superAdmin: options.superAdmin } : {}),
33
+ ...(options.resolveRoles ? { resolveRoles: options.resolveRoles } : {}),
34
+ ...(options.resourceResolver ? { resourceResolver: options.resourceResolver } : {}),
35
+ }),
36
+ ],
37
+ controllers: options.controllers ?? [],
38
+ providers: [
39
+ ...(options.policies ?? []),
40
+ ...(options.providers ?? []),
41
+ ...(options.permissionProvider
42
+ ? [{ provide: PERMISSION_PROVIDER, useValue: options.permissionProvider }]
43
+ : []),
44
+ ...(options.roleProvider ? [{ provide: ROLE_PROVIDER, useValue: options.roleProvider }] : []),
45
+ ],
46
+ }).compile();
47
+ module.useLogger(false);
48
+ await module.init();
49
+ const gate = module.get(Gate);
50
+ for (const [ability, fn] of Object.entries(options.gates ?? {}))
38
51
  gate.define(ability, fn);
39
- }
40
- return gate;
52
+ return { module, gate };
41
53
  }
42
- /**
43
- * Build a {@link BoundGate} for a fixture `user` directly — sugar over
44
- * `buildGate(options).forUser(user)` for tests that focus on a single principal.
45
- */
46
- export function buildBoundGate(user, options = {}) {
47
- return buildGate(options).forUser(user);
54
+ /** A real {@link Gate} from fixtures. See {@link createAuthzTestingModule}. */
55
+ export async function buildGate(options = {}) {
56
+ return (await createAuthzTestingModule(options)).gate;
57
+ }
58
+ /** A {@link BoundGate} for a fixture `user`: `(await buildGate(options)).forUser(user)`. */
59
+ export async function buildBoundGate(user, options = {}) {
60
+ return (await buildGate(options)).forUser(user);
48
61
  }
49
62
  function bind(gate, user) {
50
63
  return gate instanceof BoundGate ? gate : gate.forUser(user);
@@ -56,39 +69,59 @@ export class AuthzAssertionError extends Error {
56
69
  this.name = 'AuthzAssertionError';
57
70
  }
58
71
  }
72
+ function allows(gate, check) {
73
+ return gate.allows.apply(gate, check);
74
+ }
75
+ function describeCheck(check) {
76
+ if (typeof check[0] === 'function')
77
+ return `${check[0].name}.${String(check[1])}`;
78
+ const [ability, resource] = check;
79
+ return `"${ability}"${describeResource(resource)}`;
80
+ }
59
81
  /**
60
- * Assert the user CAN perform `ability` (optionally on `resource`). Resolves when
61
- * allowed; throws {@link AuthzAssertionError} when denied. Works with either a
62
- * {@link Gate} (+ `user`) or a {@link BoundGate} (pass `undefined` for `user`).
63
- *
64
- * @example await expectCan(gate, { id: 1 }, 'update', post);
65
- * @example await expectCan(boundGate, undefined, 'create');
82
+ * Assert the user CAN: `expectCan(gate, user, 'update', post)` or
83
+ * `expectCan(gate, user, PostPolicy, 'update', post)`. Throws {@link AuthzAssertionError}.
66
84
  */
67
- export async function expectCan(gate, user, ability, resource) {
68
- const allowed = await bind(gate, user).allows(ability, resource);
69
- if (!allowed) {
70
- throw new AuthzAssertionError(`Expected user to be able to "${ability}"${describeResource(resource)}, but it was DENIED.`);
85
+ export async function expectCan(gate, user, ...check) {
86
+ if (!(await allows(bind(gate, user), check))) {
87
+ throw new AuthzAssertionError(`Expected user to be able to ${describeCheck(check)}, but it was DENIED.`);
71
88
  }
72
89
  }
73
90
  /**
74
- * Assert the user CANNOT perform `ability` (optionally on `resource`). Resolves when
75
- * denied; throws {@link AuthzAssertionError} when allowed. An unresolved ability
76
- * (no policy/gate) counts as "cannot" — the deny-by-default outcome a caller wants
77
- * to assert — so this never leaks the underlying `AbilityNotResolvedException`.
91
+ * Assert the user CANNOT. An unresolved ability (no policy, gate or permission answers
92
+ * it) counts as "cannot", the deny-by-default outcome.
78
93
  */
79
- export async function expectCannot(gate, user, ability, resource) {
94
+ export async function expectCannot(gate, user, ...check) {
80
95
  let allowed;
81
96
  try {
82
- allowed = await bind(gate, user).allows(ability, resource);
97
+ allowed = await allows(bind(gate, user), check);
83
98
  }
84
99
  catch {
85
- // Unresolved/ambiguous ability → effectively "cannot" (deny by default).
86
100
  return;
87
101
  }
88
102
  if (allowed) {
89
- throw new AuthzAssertionError(`Expected user to be UNABLE to "${ability}"${describeResource(resource)}, but it was ALLOWED.`);
103
+ throw new AuthzAssertionError(`Expected user to be UNABLE to ${describeCheck(check)}, but it was ALLOWED.`);
90
104
  }
91
105
  }
106
+ /**
107
+ * What the official guard requires on `controller.method`, class-level first. Lets a test
108
+ * assert a route's protection without booting HTTP:
109
+ *
110
+ * ```ts
111
+ * expect(requirementsOf(PostController, 'publish')).toEqual([
112
+ * { policy: PermissionPolicy, ability: 'has', staticArgs: ['posts.publish'], dynamic: false },
113
+ * ]);
114
+ * ```
115
+ */
116
+ export function requirementsOf(controller, method) {
117
+ const handler = controller.prototype[method];
118
+ if (!handler)
119
+ throw new Error(`${controller.name} has no method '${method}'.`);
120
+ return routeRequirements(controller, handler).map(({ policy, ability, args }) => {
121
+ const staticArgs = getStaticCanArgs(args);
122
+ return { policy, ability, staticArgs, dynamic: args !== undefined && staticArgs === undefined };
123
+ });
124
+ }
92
125
  function describeResource(resource) {
93
126
  if (resource === undefined)
94
127
  return '';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dudousxd/nestjs-authz-testing",
3
- "version": "0.1.1",
4
- "description": "Test helpers for @dudousxd/nestjs-authz — build a Gate/BoundGate from fixture users & policies and assert with expectCan/expectCannot.",
3
+ "version": "0.2.0",
4
+ "description": "Test helpers for @dudousxd/nestjs-authz on @nestjs/authorization: a real Gate on a Nest testing module, expectCan/expectCannot, in-memory RBAC fakes, route requirement inspection.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -38,21 +38,30 @@
38
38
  "CHANGELOG.md"
39
39
  ],
40
40
  "peerDependencies": {
41
- "@dudousxd/nestjs-authz": ">=0.5.0",
41
+ "@dudousxd/nestjs-authz": ">=0.9.0 <0.10.0",
42
+ "@nestjs/authorization": "^0.1.3",
43
+ "@nestjs/common": "^12.0.0",
44
+ "@nestjs/core": "^12.0.0",
45
+ "@nestjs/testing": "^12.0.0",
42
46
  "reflect-metadata": ">=0.1.13"
43
47
  },
44
48
  "devDependencies": {
45
- "@types/node": "20.19.43",
49
+ "@nestjs/authorization": "0.1.3",
50
+ "@nestjs/common": "12.0.1",
51
+ "@nestjs/core": "12.0.1",
52
+ "@nestjs/testing": "12.0.1",
53
+ "@types/node": "22.20.5",
46
54
  "reflect-metadata": "0.2.2",
55
+ "rxjs": "7.8.2",
47
56
  "typescript": "5.9.3",
48
57
  "vitest": "3.2.7",
49
- "@dudousxd/nestjs-authz": "^0.8.1"
58
+ "@dudousxd/nestjs-authz": "^0.9.1"
50
59
  },
51
60
  "engines": {
52
- "node": ">=20"
61
+ "node": ">=22"
53
62
  },
54
63
  "scripts": {
55
- "build": "tsc -p tsconfig.json",
64
+ "build": "tsc -p tsconfig.json --incremental false",
56
65
  "test": "vitest run --passWithNoTests",
57
66
  "test:watch": "vitest",
58
67
  "typecheck": "tsc -p tsconfig.json --noEmit"