@lenne.tech/nest-server 11.12.0 → 11.13.1

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.
Files changed (98) hide show
  1. package/dist/config.env.js +1 -0
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/interfaces/server-options.interface.d.ts +16 -0
  4. package/dist/core/modules/auth/guards/roles.guard.js +1 -1
  5. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  6. package/dist/core/modules/better-auth/better-auth.config.d.ts +13 -0
  7. package/dist/core/modules/better-auth/better-auth.config.js +68 -18
  8. package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
  9. package/dist/core/modules/better-auth/better-auth.resolver.d.ts +7 -3
  10. package/dist/core/modules/better-auth/better-auth.resolver.js +16 -6
  11. package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -1
  12. package/dist/core/modules/better-auth/core-better-auth-api.middleware.d.ts +2 -2
  13. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +46 -2
  14. package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
  15. package/dist/core/modules/better-auth/core-better-auth-auth.model.d.ts +1 -0
  16. package/dist/core/modules/better-auth/core-better-auth-auth.model.js +7 -0
  17. package/dist/core/modules/better-auth/core-better-auth-auth.model.js.map +1 -1
  18. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.d.ts +48 -0
  19. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +241 -0
  20. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -0
  21. package/dist/core/modules/better-auth/core-better-auth-models.d.ts +2 -1
  22. package/dist/core/modules/better-auth/core-better-auth-models.js +8 -4
  23. package/dist/core/modules/better-auth/core-better-auth-models.js.map +1 -1
  24. package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.d.ts +18 -0
  25. package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.js +82 -0
  26. package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.js.map +1 -0
  27. package/dist/core/modules/better-auth/core-better-auth-user.mapper.d.ts +0 -1
  28. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +15 -8
  29. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
  30. package/dist/core/modules/better-auth/core-better-auth-web.helper.d.ts +4 -1
  31. package/dist/core/modules/better-auth/core-better-auth-web.helper.js +41 -30
  32. package/dist/core/modules/better-auth/core-better-auth-web.helper.js.map +1 -1
  33. package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +11 -1
  34. package/dist/core/modules/better-auth/core-better-auth.controller.js +91 -15
  35. package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
  36. package/dist/core/modules/better-auth/core-better-auth.middleware.js +93 -45
  37. package/dist/core/modules/better-auth/core-better-auth.middleware.js.map +1 -1
  38. package/dist/core/modules/better-auth/core-better-auth.module.d.ts +6 -0
  39. package/dist/core/modules/better-auth/core-better-auth.module.js +131 -25
  40. package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
  41. package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +12 -5
  42. package/dist/core/modules/better-auth/core-better-auth.resolver.js +64 -17
  43. package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
  44. package/dist/core/modules/better-auth/core-better-auth.service.d.ts +4 -1
  45. package/dist/core/modules/better-auth/core-better-auth.service.js +123 -23
  46. package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
  47. package/dist/core/modules/better-auth/index.d.ts +2 -0
  48. package/dist/core/modules/better-auth/index.js +2 -0
  49. package/dist/core/modules/better-auth/index.js.map +1 -1
  50. package/dist/core/modules/error-code/error-codes.d.ts +45 -0
  51. package/dist/core/modules/error-code/error-codes.js +40 -0
  52. package/dist/core/modules/error-code/error-codes.js.map +1 -1
  53. package/dist/core/modules/user/core-user.model.d.ts +1 -0
  54. package/dist/core/modules/user/core-user.model.js +11 -0
  55. package/dist/core/modules/user/core-user.model.js.map +1 -1
  56. package/dist/server/modules/better-auth/better-auth.controller.d.ts +3 -1
  57. package/dist/server/modules/better-auth/better-auth.controller.js +12 -3
  58. package/dist/server/modules/better-auth/better-auth.controller.js.map +1 -1
  59. package/dist/server/modules/better-auth/better-auth.resolver.d.ts +7 -3
  60. package/dist/server/modules/better-auth/better-auth.resolver.js +16 -6
  61. package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -1
  62. package/dist/server/modules/error-code/error-codes.d.ts +5 -0
  63. package/dist/server/modules/user/user.model.d.ts +5 -0
  64. package/dist/templates/email-verification-de.ejs +78 -0
  65. package/dist/templates/email-verification-en.ejs +78 -0
  66. package/dist/test/test.helper.d.ts +4 -0
  67. package/dist/test/test.helper.js +54 -1
  68. package/dist/test/test.helper.js.map +1 -1
  69. package/dist/tsconfig.build.tsbuildinfo +1 -1
  70. package/package.json +3 -11
  71. package/src/config.env.ts +2 -0
  72. package/src/core/common/interfaces/server-options.interface.ts +240 -0
  73. package/src/core/modules/auth/guards/roles.guard.ts +4 -1
  74. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +113 -0
  75. package/src/core/modules/better-auth/README.md +72 -7
  76. package/src/core/modules/better-auth/better-auth.config.ts +168 -42
  77. package/src/core/modules/better-auth/better-auth.resolver.ts +16 -5
  78. package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +65 -8
  79. package/src/core/modules/better-auth/core-better-auth-auth.model.ts +10 -0
  80. package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +433 -0
  81. package/src/core/modules/better-auth/core-better-auth-models.ts +6 -3
  82. package/src/core/modules/better-auth/core-better-auth-signup-validator.service.ts +178 -0
  83. package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +18 -14
  84. package/src/core/modules/better-auth/core-better-auth-web.helper.ts +67 -46
  85. package/src/core/modules/better-auth/core-better-auth.controller.ts +155 -16
  86. package/src/core/modules/better-auth/core-better-auth.middleware.ts +148 -70
  87. package/src/core/modules/better-auth/core-better-auth.module.ts +226 -40
  88. package/src/core/modules/better-auth/core-better-auth.resolver.ts +140 -20
  89. package/src/core/modules/better-auth/core-better-auth.service.ts +181 -25
  90. package/src/core/modules/better-auth/index.ts +2 -0
  91. package/src/core/modules/error-code/error-codes.ts +45 -0
  92. package/src/core/modules/user/core-user.model.ts +15 -0
  93. package/src/server/modules/better-auth/better-auth.controller.ts +6 -2
  94. package/src/server/modules/better-auth/better-auth.resolver.ts +16 -5
  95. package/src/templates/email-verification-de.ejs +78 -0
  96. package/src/templates/email-verification-en.ejs +78 -0
  97. package/src/test/README.md +190 -0
  98. package/src/test/test.helper.ts +82 -1
@@ -1,9 +1,11 @@
1
+ import { Optional } from '@nestjs/common';
1
2
  import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
2
3
  import { Request, Response } from 'express';
3
4
 
4
5
  import { Roles } from '../../../core/common/decorators/roles.decorator';
5
6
  import { RoleEnum } from '../../../core/common/enums/role.enum';
6
7
  import { CoreBetterAuthAuthModel } from '../../../core/modules/better-auth/core-better-auth-auth.model';
8
+ import { CoreBetterAuthEmailVerificationService } from '../../../core/modules/better-auth/core-better-auth-email-verification.service';
7
9
  import { CoreBetterAuthMigrationStatusModel } from '../../../core/modules/better-auth/core-better-auth-migration-status.model';
8
10
  import {
9
11
  CoreBetterAuth2FASetupModel,
@@ -12,6 +14,7 @@ import {
12
14
  CoreBetterAuthPasskeyModel,
13
15
  CoreBetterAuthSessionModel,
14
16
  } from '../../../core/modules/better-auth/core-better-auth-models';
17
+ import { CoreBetterAuthSignUpValidatorService } from '../../../core/modules/better-auth/core-better-auth-signup-validator.service';
15
18
  import { CoreBetterAuthUserMapper } from '../../../core/modules/better-auth/core-better-auth-user.mapper';
16
19
  import { CoreBetterAuthResolver } from '../../../core/modules/better-auth/core-better-auth.resolver';
17
20
  import { CoreBetterAuthService } from '../../../core/modules/better-auth/core-better-auth.service';
@@ -29,8 +32,13 @@ import { CoreBetterAuthService } from '../../../core/modules/better-auth/core-be
29
32
  * @example
30
33
  * ```typescript
31
34
  * // Add custom behavior after sign-up
32
- * override async betterAuthSignUp(email: string, password: string, name?: string) {
33
- * const result = await super.betterAuthSignUp(email, password, name);
35
+ * override async betterAuthSignUp(
36
+ * email: string,
37
+ * password: string,
38
+ * name?: string,
39
+ * termsAndPrivacyAccepted?: boolean,
40
+ * ) {
41
+ * const result = await super.betterAuthSignUp(email, password, name, termsAndPrivacyAccepted);
34
42
  *
35
43
  * if (result.success && result.user) {
36
44
  * await this.emailService.sendWelcomeEmail(result.user.email);
@@ -46,8 +54,10 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
46
54
  constructor(
47
55
  protected override readonly betterAuthService: CoreBetterAuthService,
48
56
  protected override readonly userMapper: CoreBetterAuthUserMapper,
57
+ @Optional() protected override readonly signUpValidator?: CoreBetterAuthSignUpValidatorService,
58
+ @Optional() protected override readonly emailVerificationService?: CoreBetterAuthEmailVerificationService,
49
59
  ) {
50
- super(betterAuthService, userMapper);
60
+ super(betterAuthService, userMapper, signUpValidator, emailVerificationService);
51
61
  }
52
62
 
53
63
  // ===========================================================================
@@ -112,7 +122,7 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
112
122
  override async betterAuthSignIn(
113
123
  @Args('email') email: string,
114
124
  @Args('password') password: string,
115
- @Context() ctx: { req: Request; res: Response },
125
+ @Context() ctx?: { req: Request; res: Response },
116
126
  ): Promise<CoreBetterAuthAuthModel> {
117
127
  return super.betterAuthSignIn(email, password, ctx);
118
128
  }
@@ -125,8 +135,9 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
125
135
  @Args('email') email: string,
126
136
  @Args('password') password: string,
127
137
  @Args('name', { nullable: true }) name?: string,
138
+ @Args('termsAndPrivacyAccepted', { nullable: true }) termsAndPrivacyAccepted?: boolean,
128
139
  ): Promise<CoreBetterAuthAuthModel> {
129
- return super.betterAuthSignUp(email, password, name);
140
+ return super.betterAuthSignUp(email, password, name, termsAndPrivacyAccepted);
130
141
  }
131
142
 
132
143
  @Mutation(() => Boolean, { description: 'Sign out via Better-Auth' })
@@ -0,0 +1,78 @@
1
+ <!DOCTYPE html>
2
+ <html lang="de">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>E-Mail-Adresse bestätigen</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
10
+ line-height: 1.6;
11
+ color: #333;
12
+ max-width: 600px;
13
+ margin: 0 auto;
14
+ padding: 20px;
15
+ }
16
+ .container {
17
+ background-color: #ffffff;
18
+ border-radius: 8px;
19
+ padding: 30px;
20
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
21
+ }
22
+ h1 {
23
+ color: #2563eb;
24
+ margin-bottom: 20px;
25
+ }
26
+ .button {
27
+ display: inline-block;
28
+ background-color: #2563eb;
29
+ color: #ffffff !important;
30
+ text-decoration: none;
31
+ padding: 12px 30px;
32
+ border-radius: 6px;
33
+ font-weight: 600;
34
+ margin: 20px 0;
35
+ }
36
+ .button:hover {
37
+ background-color: #1d4ed8;
38
+ }
39
+ .footer {
40
+ margin-top: 30px;
41
+ padding-top: 20px;
42
+ border-top: 1px solid #e5e7eb;
43
+ font-size: 14px;
44
+ color: #6b7280;
45
+ }
46
+ .link-fallback {
47
+ font-size: 12px;
48
+ color: #6b7280;
49
+ word-break: break-all;
50
+ margin-top: 15px;
51
+ }
52
+ </style>
53
+ </head>
54
+ <body>
55
+ <div class="container">
56
+ <h1>Hallo <%= name %>,</h1>
57
+
58
+ <p>Vielen Dank für Ihre Registrierung bei <%= appName %>!</p>
59
+
60
+ <p>Bitte klicken Sie auf die Schaltfläche unten, um Ihre E-Mail-Adresse zu bestätigen:</p>
61
+
62
+ <p style="text-align: center;">
63
+ <a href="<%= link %>" class="button">E-Mail-Adresse bestätigen</a>
64
+ </p>
65
+
66
+ <p>Dieser Link ist <strong><%= expiresIn %></strong> gültig.</p>
67
+
68
+ <div class="footer">
69
+ <p>Wenn Sie kein Konto erstellt haben, können Sie diese E-Mail ignorieren.</p>
70
+
71
+ <p class="link-fallback">
72
+ Falls die Schaltfläche nicht funktioniert, kopieren Sie diesen Link in Ihren Browser:<br>
73
+ <a href="<%= link %>"><%= link %></a>
74
+ </p>
75
+ </div>
76
+ </div>
77
+ </body>
78
+ </html>
@@ -0,0 +1,78 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Verify your email address</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
10
+ line-height: 1.6;
11
+ color: #333;
12
+ max-width: 600px;
13
+ margin: 0 auto;
14
+ padding: 20px;
15
+ }
16
+ .container {
17
+ background-color: #ffffff;
18
+ border-radius: 8px;
19
+ padding: 30px;
20
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
21
+ }
22
+ h1 {
23
+ color: #2563eb;
24
+ margin-bottom: 20px;
25
+ }
26
+ .button {
27
+ display: inline-block;
28
+ background-color: #2563eb;
29
+ color: #ffffff !important;
30
+ text-decoration: none;
31
+ padding: 12px 30px;
32
+ border-radius: 6px;
33
+ font-weight: 600;
34
+ margin: 20px 0;
35
+ }
36
+ .button:hover {
37
+ background-color: #1d4ed8;
38
+ }
39
+ .footer {
40
+ margin-top: 30px;
41
+ padding-top: 20px;
42
+ border-top: 1px solid #e5e7eb;
43
+ font-size: 14px;
44
+ color: #6b7280;
45
+ }
46
+ .link-fallback {
47
+ font-size: 12px;
48
+ color: #6b7280;
49
+ word-break: break-all;
50
+ margin-top: 15px;
51
+ }
52
+ </style>
53
+ </head>
54
+ <body>
55
+ <div class="container">
56
+ <h1>Hello <%= name %>,</h1>
57
+
58
+ <p>Thank you for registering with <%= appName %>!</p>
59
+
60
+ <p>Please click the button below to verify your email address:</p>
61
+
62
+ <p style="text-align: center;">
63
+ <a href="<%= link %>" class="button">Verify Email Address</a>
64
+ </p>
65
+
66
+ <p>This link will expire in <strong><%= expiresIn %></strong>.</p>
67
+
68
+ <div class="footer">
69
+ <p>If you did not create an account, you can safely ignore this email.</p>
70
+
71
+ <p class="link-fallback">
72
+ If the button doesn't work, copy and paste this link into your browser:<br>
73
+ <a href="<%= link %>"><%= link %></a>
74
+ </p>
75
+ </div>
76
+ </div>
77
+ </body>
78
+ </html>
@@ -0,0 +1,190 @@
1
+ # TestHelper Reference
2
+
3
+ The `TestHelper` class provides utilities for testing GraphQL and REST APIs in `@lenne.tech/nest-server` projects.
4
+
5
+ ## Initialization
6
+
7
+ ```typescript
8
+ import { TestHelper } from '@lenne.tech/nest-server';
9
+
10
+ const app = moduleFixture.createNestApplication();
11
+ await app.init();
12
+ const testHelper = new TestHelper(app);
13
+
14
+ // With WebSocket support for subscriptions
15
+ const testHelper = new TestHelper(app, 'ws://127.0.0.1:3030/graphql');
16
+ ```
17
+
18
+ ## REST API Testing (`testHelper.rest()`)
19
+
20
+ ```typescript
21
+ const result = await testHelper.rest('/endpoint', options);
22
+ ```
23
+
24
+ ### TestRestOptions
25
+
26
+ | Option | Type | Default | Description |
27
+ |--------|------|---------|-------------|
28
+ | `method` | `'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'` | `'GET'` | HTTP method |
29
+ | `token` | `string` | `null` | Bearer token via Authorization header |
30
+ | `cookies` | `string \| Record<string, string>` | - | Cookie-based authentication (see below) |
31
+ | `headers` | `Record<string, string>` | - | Custom request headers |
32
+ | `payload` | `any` | `null` | Request body |
33
+ | `statusCode` | `number` | `200` | Expected HTTP status code |
34
+ | `returnResponse` | `boolean` | `false` | Return full response including headers |
35
+ | `attachments` | `Record<string, string>` | - | File uploads (key: field name, value: file path) |
36
+ | `log` | `boolean` | `false` | Log request config to console |
37
+ | `logError` | `boolean` | `false` | Log error details when status >= 400 |
38
+
39
+ ## Cookie Authentication
40
+
41
+ The `cookies` option supports three modes with **auto-detection**:
42
+
43
+ ### 1. Plain Session Token (Auto-Detection)
44
+
45
+ When a string **without** `=` or `;` is provided, it is automatically recognized as a session token and converted via `buildBetterAuthCookies()`:
46
+
47
+ ```typescript
48
+ // Auto-detection: plain token -> sets iam.session_token + token cookies
49
+ const result = await testHelper.rest('/endpoint', {
50
+ cookies: sessionToken,
51
+ });
52
+ ```
53
+
54
+ This is equivalent to:
55
+ ```typescript
56
+ cookies: { 'iam.session_token': sessionToken, 'token': sessionToken }
57
+ ```
58
+
59
+ ### 2. Explicit Cookie Pairs
60
+
61
+ ```typescript
62
+ const result = await testHelper.rest('/endpoint', {
63
+ cookies: { 'iam.session_token': token, 'custom-cookie': 'value' },
64
+ });
65
+ ```
66
+
67
+ ### 3. Raw Cookie String
68
+
69
+ When a string **with** `=` or `;` is provided, it is used as-is:
70
+
71
+ ```typescript
72
+ const result = await testHelper.rest('/endpoint', {
73
+ cookies: 'iam.session_token=abc; token=xyz',
74
+ });
75
+ ```
76
+
77
+ ### `token` vs `cookies`
78
+
79
+ | Option | Transport | Use Case |
80
+ |--------|-----------|----------|
81
+ | `token` | `Authorization: Bearer <token>` header | JWT authentication |
82
+ | `cookies` | `Cookie` header | Session-based authentication (BetterAuth) |
83
+
84
+ Both can be used simultaneously without conflict - `token` sets the Authorization header while `cookies` sets the Cookie header.
85
+
86
+ ## Static Helper Methods
87
+
88
+ ### `TestHelper.buildBetterAuthCookies(sessionToken, basePath?)`
89
+
90
+ Build a cookie Record for BetterAuth session authentication:
91
+
92
+ ```typescript
93
+ const cookies = TestHelper.buildBetterAuthCookies('session-token-value');
94
+ // Result: { 'iam.session_token': 'session-token-value', 'token': 'session-token-value' }
95
+
96
+ // Custom base path
97
+ const cookies = TestHelper.buildBetterAuthCookies('token', 'auth');
98
+ // Result: { 'auth.session_token': 'token', 'token': 'token' }
99
+ ```
100
+
101
+ ### `TestHelper.extractSessionToken(response, cookieName?)`
102
+
103
+ Extract a session token from Set-Cookie headers. Handles signed cookies (`value.signature` format):
104
+
105
+ ```typescript
106
+ const response = await testHelper.rest('/iam/sign-in/email', {
107
+ method: 'POST',
108
+ payload: { email, password },
109
+ returnResponse: true,
110
+ });
111
+ const sessionToken = TestHelper.extractSessionToken(response);
112
+ // Returns the token value from 'iam.session_token' cookie
113
+
114
+ // Custom cookie name
115
+ const token = TestHelper.extractSessionToken(response, 'custom.session_token');
116
+ ```
117
+
118
+ ### `TestHelper.extractCookies(response)`
119
+
120
+ Extract all Set-Cookie values as a `Record<name, value>`:
121
+
122
+ ```typescript
123
+ const response = await testHelper.rest('/iam/sign-in/email', {
124
+ method: 'POST',
125
+ payload: { email, password },
126
+ returnResponse: true,
127
+ });
128
+ const cookies = TestHelper.extractCookies(response);
129
+ // Result: { 'iam.session_token': 'abc.sig', 'token': 'abc.sig', ... }
130
+ ```
131
+
132
+ ## Practical Examples
133
+
134
+ ### JWT Authentication
135
+
136
+ ```typescript
137
+ const signIn = await testHelper.rest('/iam/sign-in/email', {
138
+ method: 'POST',
139
+ payload: { email: 'user@test.com', password: 'Password123!' },
140
+ });
141
+ const jwtToken = signIn.token;
142
+
143
+ await testHelper.rest('/protected-endpoint', {
144
+ token: jwtToken,
145
+ });
146
+ ```
147
+
148
+ ### Cookie Authentication (Auto-Detection)
149
+
150
+ ```typescript
151
+ // Get session token from database after sign-in
152
+ const session = await db.collection('session').findOne({ userId: user._id });
153
+
154
+ // Use session token with auto-detection
155
+ await testHelper.rest('/protected-endpoint', {
156
+ cookies: session.token, // Auto -> iam.session_token=...; token=...
157
+ });
158
+ ```
159
+
160
+ ### Extract Session Token from Response
161
+
162
+ ```typescript
163
+ const response = await testHelper.rest('/iam/sign-in/email', {
164
+ method: 'POST',
165
+ payload: { email, password },
166
+ returnResponse: true,
167
+ });
168
+ const sessionToken = TestHelper.extractSessionToken(response);
169
+
170
+ // Use extracted token for subsequent requests
171
+ await testHelper.rest('/protected-endpoint', {
172
+ cookies: sessionToken,
173
+ });
174
+ ```
175
+
176
+ ## GraphQL Testing (`testHelper.graphQl()`)
177
+
178
+ ```typescript
179
+ const result = await testHelper.graphQl({
180
+ name: 'findUsers',
181
+ type: TestGraphQLType.QUERY,
182
+ arguments: { filter: { email: { eq: 'test@test.com' } } },
183
+ fields: ['id', 'email', 'name'],
184
+ }, {
185
+ token: jwtToken,
186
+ statusCode: 200,
187
+ });
188
+ ```
189
+
190
+ See `TestGraphQLConfig` and `TestGraphQLOptions` interfaces in `test.helper.ts` for full configuration options.
@@ -129,6 +129,17 @@ export interface TestGraphQLVariable {
129
129
  */
130
130
  export interface TestRestOptions {
131
131
  attachments?: Record<string, string>;
132
+
133
+ /**
134
+ * Cookies for the request. Three usage modes:
135
+ * - string (plain token without = or ;): Auto-detected as session token
136
+ * and converted via buildBetterAuthCookies() to all relevant cookie names
137
+ * (iam.session_token, token)
138
+ * - string (with = or ;): Used as-is as a cookie string
139
+ * - Record<string, string>: Key-value pairs joined into a cookie string
140
+ */
141
+ cookies?: Record<string, string> | string;
142
+
132
143
  headers?: Record<string, string>;
133
144
  log?: boolean;
134
145
  logError?: boolean;
@@ -404,10 +415,11 @@ export class TestHelper {
404
415
  };
405
416
 
406
417
  // Init vars
407
- const { attachments, log, logError, returnResponse, statusCode, token } = config;
418
+ const { attachments, cookies, log, logError, returnResponse, statusCode, token } = config;
408
419
 
409
420
  // Request configuration
410
421
  const requestConfig: any = {
422
+ cookies,
411
423
  headers: config.headers,
412
424
  method: config.method,
413
425
  url,
@@ -560,6 +572,24 @@ export class TestHelper {
560
572
  request.set('Authorization', `bearer ${token}`);
561
573
  }
562
574
 
575
+ // Cookies
576
+ if (requestConfig.cookies) {
577
+ let cookieString: string;
578
+ if (typeof requestConfig.cookies === 'string') {
579
+ // Auto-detect: plain token (no = or ;) vs formatted cookie string
580
+ if (!requestConfig.cookies.includes('=') && !requestConfig.cookies.includes(';')) {
581
+ // Plain session token -> auto-build BetterAuth cookies
582
+ const cookieRecord = TestHelper.buildBetterAuthCookies(requestConfig.cookies);
583
+ cookieString = Object.entries(cookieRecord).map(([k, v]) => `${k}=${v}`).join('; ');
584
+ } else {
585
+ cookieString = requestConfig.cookies;
586
+ }
587
+ } else {
588
+ cookieString = Object.entries(requestConfig.cookies).map(([k, v]) => `${k}=${v}`).join('; ');
589
+ }
590
+ request.set('Cookie', cookieString);
591
+ }
592
+
563
593
  // Headers
564
594
  if (requestConfig.headers) {
565
595
  for (const [key, value] of Object.entries(requestConfig.headers)) {
@@ -724,6 +754,57 @@ export class TestHelper {
724
754
  return messages;
725
755
  }
726
756
 
757
+ /**
758
+ * Build a cookie Record for BetterAuth session authentication.
759
+ * Sets the token in all relevant cookie names for compatibility.
760
+ */
761
+ static buildBetterAuthCookies(sessionToken: string, basePath: string = 'iam'): Record<string, string> {
762
+ return {
763
+ [`${basePath}.session_token`]: sessionToken,
764
+ 'token': sessionToken,
765
+ };
766
+ }
767
+
768
+ /**
769
+ * Extract all Set-Cookie values from a supertest response as a Record<name, value>.
770
+ */
771
+ static extractCookies(response: any): Record<string, string> {
772
+ const cookies: Record<string, string> = {};
773
+ const setCookieHeaders = response?.headers?.['set-cookie'];
774
+ if (!setCookieHeaders) {
775
+ return cookies;
776
+ }
777
+ const headerArray = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
778
+ for (const cookie of headerArray) {
779
+ const parts = cookie.split(';')[0];
780
+ const eqIndex = parts.indexOf('=');
781
+ if (eqIndex > 0) {
782
+ const name = parts.substring(0, eqIndex).trim();
783
+ const value = parts.substring(eqIndex + 1).trim();
784
+ cookies[name] = value;
785
+ }
786
+ }
787
+ return cookies;
788
+ }
789
+
790
+ /**
791
+ * Extract a session token from Set-Cookie headers of a supertest response.
792
+ * Handles signed cookies (value.signature format) by returning only the value part.
793
+ */
794
+ static extractSessionToken(response: any, cookieName: string = 'iam.session_token'): null | string {
795
+ const cookies = TestHelper.extractCookies(response);
796
+ const value = cookies[cookieName];
797
+ if (!value) {
798
+ return null;
799
+ }
800
+ // Handle signed cookies: value.signature -> return value
801
+ const dotIndex = value.lastIndexOf('.');
802
+ if (dotIndex > 0 && dotIndex < value.length - 1) {
803
+ return value.substring(0, dotIndex);
804
+ }
805
+ return value;
806
+ }
807
+
727
808
  /**
728
809
  * Convert JWT into to object
729
810
  * @param token