@teleporthq/teleport-plugin-next-workflows 0.43.45 → 0.43.46

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.
@@ -0,0 +1,130 @@
1
+ import { UIDLAuthentication } from '@teleporthq/teleport-types'
2
+ import { generateMiddlewareFile } from '../../src/auth-generator'
3
+
4
+ /**
5
+ * Loads the middleware `generateMiddlewareFile` emits and runs it against a
6
+ * synthetic request, so tests can assert on BEHAVIOUR ("does a guest reach
7
+ * /orders/ORD-42?") instead of grepping the generated source.
8
+ *
9
+ * The emitted file is an ES module that imports `next/server` and
10
+ * `next-auth/jwt`; neither is installed in this package, so the loader strips
11
+ * the module boundary and injects the pieces the middleware actually uses.
12
+ * `getToken` always resolves to `null` — the generated code then falls back to
13
+ * cookie presence plus `/api/auth/session`, which the fetch stub answers, so
14
+ * both the authenticated and the anonymous path stay exercisable.
15
+ */
16
+
17
+ export interface MiddlewareResult {
18
+ kind: 'next' | 'redirect'
19
+ /** Only set for `kind: 'redirect'`. */
20
+ location?: string
21
+ }
22
+
23
+ export interface MiddlewareSessionUser {
24
+ id?: string
25
+ role?: string
26
+ }
27
+
28
+ export interface MiddlewareRequestOptions {
29
+ /** Session user the stubbed `/api/auth/session` returns; omit for a guest. */
30
+ sessionUser?: MiddlewareSessionUser | null
31
+ origin?: string
32
+ }
33
+
34
+ interface RawMiddlewareResult {
35
+ __kind: 'next' | 'redirect'
36
+ __location?: string
37
+ }
38
+
39
+ type MiddlewareFn = (request: unknown) => Promise<RawMiddlewareResult>
40
+
41
+ type MiddlewareFactory = (
42
+ nextResponse: unknown,
43
+ getToken: unknown,
44
+ fetchImpl: unknown
45
+ ) => MiddlewareFn
46
+
47
+ const stripModuleSyntax = (code: string): string =>
48
+ code
49
+ .replace("import { NextResponse } from 'next/server';", '')
50
+ .replace("import { getToken } from 'next-auth/jwt';", '')
51
+ .replace('export default middleware;', '')
52
+ .replace('export const config = {', 'const config = {')
53
+
54
+ /**
55
+ * The route table the generated middleware gates on. Parsed out of the emitted
56
+ * source so assertions can target the actual map instead of substring-matching
57
+ * a file that also contains comments and the self-guarded route list.
58
+ */
59
+ export const extractProtectedRoutes = (
60
+ code: string
61
+ ): Record<string, { requiresAuth: boolean; allowedRoles: string[] }> => {
62
+ // Non-greedy up to the first `};`. `JSON.stringify(…, null, 2)` never emits
63
+ // that sequence inside the object, so the first hit is the real terminator —
64
+ // and the empty-map case (`= {};`) is covered by the same pattern.
65
+ const match = code.match(/const protectedRoutes = (\{[\s\S]*?\});/)
66
+ if (!match) {
67
+ throw new Error('generated middleware has no protectedRoutes declaration')
68
+ }
69
+ return JSON.parse(match[1])
70
+ }
71
+
72
+ /** Route patterns whose page-load SQL is the access control. */
73
+ export const extractSelfGuardedRoutes = (code: string): string[] => {
74
+ const match = code.match(/const selfGuardedRoutes = (\[[\s\S]*?\]);/)
75
+ if (!match) {
76
+ throw new Error('generated middleware has no selfGuardedRoutes declaration')
77
+ }
78
+ return JSON.parse(match[1])
79
+ }
80
+
81
+ const NEXT_RESPONSE_STUB = {
82
+ next: (): RawMiddlewareResult => ({ __kind: 'next' }),
83
+ redirect: (url: URL): RawMiddlewareResult => ({
84
+ __kind: 'redirect',
85
+ __location: String(url),
86
+ }),
87
+ }
88
+
89
+ /**
90
+ * Compiles the middleware once for an auth config. Reuse the returned function
91
+ * across requests in a test to avoid re-generating the file per assertion.
92
+ */
93
+ export const compileGeneratedMiddleware = (auth: UIDLAuthentication) => {
94
+ const factory = new Function(
95
+ 'NextResponse',
96
+ 'getToken',
97
+ 'fetch',
98
+ `${stripModuleSyntax(generateMiddlewareFile(auth))}; return middleware;`
99
+ ) as unknown as MiddlewareFactory
100
+
101
+ return async (
102
+ pathname: string,
103
+ options: MiddlewareRequestOptions = {}
104
+ ): Promise<MiddlewareResult> => {
105
+ const origin = options.origin || 'https://shop.test'
106
+ const sessionUser = options.sessionUser ?? null
107
+ const cookieHeader = sessionUser ? 'next-auth.session-token=stub' : ''
108
+
109
+ const fetchImpl = async () => ({
110
+ ok: true,
111
+ json: async () => ({ user: sessionUser }),
112
+ })
113
+
114
+ const getTokenStub = async (): Promise<null> => null
115
+ const middleware = factory(NEXT_RESPONSE_STUB, getTokenStub, fetchImpl)
116
+ const result = await middleware({
117
+ nextUrl: { pathname },
118
+ url: `${origin}${pathname}`,
119
+ cookies: {
120
+ get: (name: string) =>
121
+ sessionUser && name === 'next-auth.session-token' ? { name, value: 'stub' } : undefined,
122
+ },
123
+ headers: { get: (name: string) => (name === 'cookie' ? cookieHeader : '') },
124
+ })
125
+
126
+ return result.__kind === 'redirect'
127
+ ? { kind: 'redirect', location: result.__location }
128
+ : { kind: 'next' }
129
+ }
130
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The form-submitted trigger must expose its fields where generated validators
3
+ * actually look for them.
4
+ *
5
+ * THE DEFECT (run 4b049fe6, reported: "when I update a guild and click Save
6
+ * changes I get 'Guild Name is required' even though the name is added"):
7
+ *
8
+ * Edit Guild Submit → validate-form:
9
+ * const form = params[0] || {}
10
+ * if (!form["name"]) return { isValid: false, error: "Guild Name is required" }
11
+ *
12
+ * `params[0]` is the trigger — the executor writes `context[triggerNodeId]`
13
+ * first — and the trigger carried the fields one level down under `formData`.
14
+ * `params[0].name` was `undefined`, so every save reported the filled-in name as
15
+ * missing. `general-extract-form-data` has always returned its fields flat for
16
+ * exactly this reason; the trigger now matches it.
17
+ *
18
+ * These tests EXECUTE the generated handler source rather than asserting on
19
+ * strings, so they fail if the emitted expression ever stops behaving.
20
+ */
21
+
22
+ /** Evaluate the `triggerContext = …` expression the generator emits. */
23
+ function evaluateTriggerContext(
24
+ source: string,
25
+ formData: Record<string, unknown>
26
+ ): Record<string, unknown> {
27
+ const match = /const triggerContext = ([\s\S]*?);\n/.exec(source)
28
+ if (!match) {
29
+ throw new Error('no triggerContext assignment found in generated source')
30
+ }
31
+ const expression = match[1]
32
+ // The generated code reads `event.target`; substitute a stand-in.
33
+ .replace(/event\.target/g, '__el')
34
+ const fn = new Function(
35
+ 'formData',
36
+ '__el',
37
+ `const Date = { now: () => 1234 }; return (${expression});`
38
+ )
39
+ return fn(formData, { tagName: 'FORM' }) as Record<string, unknown>
40
+ }
41
+
42
+ describe('event-form-submitted trigger context', () => {
43
+ const SOURCE = [
44
+ 'async function(event) {',
45
+ ' event.preventDefault();',
46
+ ' const formData = {};',
47
+ ' const fd = new FormData(event.target);',
48
+ ' fd.forEach(function(v, k) { formData[k] = v; });',
49
+ " const triggerContext = Object.assign({}, formData, { fields: formData, formData: formData, formId: 'edit-guild-form', triggerElement: event.target, element: event.target, timestamp: Date.now() });",
50
+ '',
51
+ ].join('\n')
52
+
53
+ it('exposes each submitted field at the top level', () => {
54
+ const ctx = evaluateTriggerContext(SOURCE, { name: 'Order of the Light', server: 'Area 52' })
55
+ expect(ctx.name).toBe('Order of the Light')
56
+ expect(ctx.server).toBe('Area 52')
57
+ })
58
+
59
+ it('still exposes them under formData and fields', () => {
60
+ const ctx = evaluateTriggerContext(SOURCE, { name: 'Order of the Light' })
61
+ expect((ctx.formData as Record<string, unknown>).name).toBe('Order of the Light')
62
+ expect((ctx.fields as Record<string, unknown>).name).toBe('Order of the Light')
63
+ })
64
+
65
+ it('keeps the reserved keys even when a field is named after one', () => {
66
+ // The spread comes FIRST, so a field literally called `formId` or `element`
67
+ // cannot shadow the runtime's own keys.
68
+ const ctx = evaluateTriggerContext(SOURCE, {
69
+ formId: 'attacker',
70
+ element: 'attacker',
71
+ timestamp: 'attacker',
72
+ formData: 'attacker',
73
+ })
74
+ expect(ctx.formId).toBe('edit-guild-form')
75
+ expect(ctx.element).toEqual({ tagName: 'FORM' })
76
+ expect(ctx.timestamp).toBe(1234)
77
+ expect(typeof ctx.formData).toBe('object')
78
+ })
79
+
80
+ it('reproduces the exact Edit Guild validator, which now passes', () => {
81
+ const ctx = evaluateTriggerContext(SOURCE, {
82
+ name: 'Order of the Light',
83
+ server: 'Area 52',
84
+ leader_account: 'Thrall',
85
+ visibility: 'Public',
86
+ })
87
+ const validate = (params: unknown[]) => {
88
+ const form = (params[0] || {}) as Record<string, string>
89
+ const required = ['name', 'server', 'leader_account', 'visibility']
90
+ for (const key of required) {
91
+ const value = form[key]
92
+ if (!value || (typeof value === 'string' && value.trim() === '')) {
93
+ return { isValid: false, error: `${key} is required` }
94
+ }
95
+ }
96
+ return { isValid: true, error: '' }
97
+ }
98
+ expect(validate([ctx])).toEqual({ isValid: true, error: '' })
99
+ })
100
+ })
@@ -0,0 +1,258 @@
1
+ import { compileGeneratedMiddleware } from './_helpers/run-generated-middleware'
2
+
3
+ /**
4
+ * Guest checkout regression, end to end through the GENERATED middleware.
5
+ *
6
+ * The e-commerce list/details pair deliberately shares a static base — the
7
+ * order history is served from `/orders` and a single order from
8
+ * `/orders/[order_number]` — so a reader can drop the last path segment to get
9
+ * back to the listing. The listing legitimately requires auth (a guest has no
10
+ * order history), the details page does not: its page-load SQL matches
11
+ * `user_id = <session user>` OR `user_id = <anonymous localStorage UUID>`, and
12
+ * a guest order is stamped with the latter.
13
+ *
14
+ * `generateMiddlewareFile` already skipped row-owned self-guarded pages from
15
+ * `protectedRoutes`, but that was not enough: the middleware matches a request
16
+ * against every protected route as a PREFIX, so `/orders/ORD-42` matched the
17
+ * listing's `/orders` entry and every guest was redirected to
18
+ * `/sign-in?callbackUrl=/orders/ORD-42` — right after paying, and right after
19
+ * the COD "order placed" toast. Buyers with a stale session cookie never saw
20
+ * it, which is why it looked intermittent.
21
+ *
22
+ * The fix emits the self-guarded page's full route pattern and waives ONLY
23
+ * inherited, role-free protection for paths that pattern matches.
24
+ */
25
+
26
+ const baseAuth: any = {
27
+ enabled: true,
28
+ dataSourceId: 'ds-1',
29
+ dataSourceType: 'postgres',
30
+ passwordAuthEnabled: true,
31
+ providers: [],
32
+ roles: ['admin'],
33
+ tables: {},
34
+ folderProtection: {},
35
+ authPages: {
36
+ signIn: { pageId: 'sign-in', pageName: 'sign-in', route: '/sign-in' },
37
+ signUp: { pageId: 'sign-up', pageName: 'sign-up', route: '/sign-up' },
38
+ },
39
+ callbackBaseUrl: '',
40
+ envKeys: {},
41
+ customUserProperties: [],
42
+ }
43
+
44
+ const ordersListProtection = {
45
+ requiresAuth: true,
46
+ allowedRoles: [] as string[],
47
+ pageName: 'orders-list',
48
+ route: '/orders',
49
+ }
50
+
51
+ const orderDetailsProtection = {
52
+ requiresAuth: true,
53
+ allowedRoles: [] as string[],
54
+ pageName: 'order-details',
55
+ route: '/orders',
56
+ routePattern: '/orders/[order_number]',
57
+ rowOwnerColumn: 'user_id',
58
+ rowOwnerTable: 'teleport_orders',
59
+ rowOwnerDifferentiator: 'order_number',
60
+ }
61
+
62
+ const storeAuth = (overrides: Record<string, unknown> = {}) =>
63
+ ({
64
+ ...baseAuth,
65
+ pageProtection: {
66
+ OL: ordersListProtection,
67
+ OD: orderDetailsProtection,
68
+ ADMIN: {
69
+ requiresAuth: true,
70
+ allowedRoles: ['admin'],
71
+ pageName: 'admin',
72
+ route: '/admin',
73
+ },
74
+ },
75
+ ...overrides,
76
+ } as any)
77
+
78
+ describe('generated middleware: guest access to a row-owned order-details route', () => {
79
+ const run = compileGeneratedMiddleware(storeAuth())
80
+
81
+ it('lets an anonymous buyer reach the order they just placed', async () => {
82
+ // The single path both checkout branches land on: the COD branch navigates
83
+ // here directly, and Stripe/PayPal return to `<this>?payment=success`
84
+ // (`nextUrl.pathname` excludes the query, so the decision is made here).
85
+ expect(await run('/orders/ORD-42')).toEqual({ kind: 'next' })
86
+ })
87
+
88
+ it('matches the same order route with a trailing slash', async () => {
89
+ // Next.js serves both forms; matching the raw pathname would make the
90
+ // slashed variant miss the self-guarded pattern and inherit the listing's
91
+ // protection.
92
+ expect(await run('/orders/ORD-42/')).toEqual({ kind: 'next' })
93
+ })
94
+
95
+ it('URL-encoded order numbers still resolve to the details route', async () => {
96
+ expect(await run('/orders/ORD%2F42')).toEqual({ kind: 'next' })
97
+ })
98
+
99
+ it('still forces sign-in on the orders LISTING itself', async () => {
100
+ const result = await run('/orders')
101
+ expect(result.kind).toBe('redirect')
102
+ expect(result.location).toContain('/sign-in')
103
+ })
104
+
105
+ it('does not waive protection for a deeper path the details page never serves', async () => {
106
+ // `/orders/[order_number]` compiles to a single-segment matcher, so a
107
+ // crafted `/orders/ORD-42/anything` keeps the listing's protection.
108
+ const result = await run('/orders/ORD-42/edit')
109
+ expect(result.kind).toBe('redirect')
110
+ expect(result.location).toContain('/sign-in')
111
+ })
112
+
113
+ it('keeps the admin subtree gated', async () => {
114
+ expect((await run('/admin')).kind).toBe('redirect')
115
+ expect((await run('/admin/orders')).kind).toBe('redirect')
116
+ })
117
+
118
+ it('lets a signed-in buyer reach their order history', async () => {
119
+ expect(await run('/orders', { sessionUser: { id: 'u-1', role: 'user' } })).toEqual({
120
+ kind: 'next',
121
+ })
122
+ })
123
+
124
+ it('still bounces a non-admin off the admin subtree', async () => {
125
+ const result = await run('/admin/orders', { sessionUser: { id: 'u-1', role: 'user' } })
126
+ expect(result.kind).toBe('redirect')
127
+ })
128
+ })
129
+
130
+ describe('generated middleware: self-guard waiver stays narrow', () => {
131
+ it('does not waive protection when the ancestor route demands a role', async () => {
132
+ // A staff-only subtree is an absolute gate the row-level WHERE clause
133
+ // cannot reproduce, so the guest must NOT slip through it.
134
+ const run = compileGeneratedMiddleware(
135
+ storeAuth({
136
+ pageProtection: {
137
+ STAFF_LIST: {
138
+ requiresAuth: true,
139
+ allowedRoles: ['staff'],
140
+ pageName: 'staff-orders',
141
+ route: '/staff-orders',
142
+ },
143
+ STAFF_DETAILS: {
144
+ requiresAuth: true,
145
+ allowedRoles: [],
146
+ pageName: 'staff-order-details',
147
+ route: '/staff-orders',
148
+ routePattern: '/staff-orders/[order_number]',
149
+ rowOwnerColumn: 'user_id',
150
+ rowOwnerDifferentiator: 'order_number',
151
+ },
152
+ },
153
+ })
154
+ )
155
+
156
+ const result = await run('/staff-orders/ORD-42')
157
+ expect(result.kind).toBe('redirect')
158
+ expect(result.location).toContain('/sign-in')
159
+ })
160
+
161
+ it("does not waive a page's OWN exact protection", async () => {
162
+ // Contrived but load-bearing: if some other protected page is published at
163
+ // exactly the path a self-guarded pattern matches, that page's own entry
164
+ // wins. Only INHERITED protection is waivable.
165
+ const run = compileGeneratedMiddleware(
166
+ storeAuth({
167
+ pageProtection: {
168
+ OL: ordersListProtection,
169
+ OD: orderDetailsProtection,
170
+ CLASH: {
171
+ requiresAuth: true,
172
+ allowedRoles: [],
173
+ pageName: 'orders-archive',
174
+ route: '/orders/archive',
175
+ },
176
+ },
177
+ })
178
+ )
179
+
180
+ const result = await run('/orders/archive')
181
+ expect(result.kind).toBe('redirect')
182
+ expect(result.location).toContain('/sign-in')
183
+ })
184
+
185
+ it('reconstructs the pattern for UIDLs emitted before `routePattern` existed', async () => {
186
+ // Older projects carry only `route` + `rowOwnerDifferentiator`. As long as
187
+ // the route is the real static base, the pattern is derivable.
188
+ const run = compileGeneratedMiddleware(
189
+ storeAuth({
190
+ pageProtection: {
191
+ OL: ordersListProtection,
192
+ OD: {
193
+ requiresAuth: true,
194
+ allowedRoles: [],
195
+ pageName: 'order-details',
196
+ route: '/orders',
197
+ rowOwnerColumn: 'user_id',
198
+ rowOwnerDifferentiator: 'order_number',
199
+ },
200
+ },
201
+ })
202
+ )
203
+
204
+ expect(await run('/orders/ORD-42')).toEqual({ kind: 'next' })
205
+ expect((await run('/orders')).kind).toBe('redirect')
206
+ })
207
+
208
+ it('leaves projects without any self-guarded page completely unchanged', async () => {
209
+ const run = compileGeneratedMiddleware({
210
+ ...baseAuth,
211
+ pageProtection: {
212
+ ACCOUNT: {
213
+ requiresAuth: true,
214
+ allowedRoles: [],
215
+ pageName: 'account',
216
+ route: '/account',
217
+ },
218
+ },
219
+ } as any)
220
+
221
+ expect((await run('/account')).kind).toBe('redirect')
222
+ expect((await run('/account/settings')).kind).toBe('redirect')
223
+ expect(await run('/about')).toEqual({ kind: 'next' })
224
+ })
225
+ })
226
+
227
+ describe('generated middleware: two pages claiming one route key', () => {
228
+ it('merges their rules instead of letting document order pick a winner', async () => {
229
+ // A list/details pair shares its static base by design, and a user can also
230
+ // point two pages at the same custom URL. One prefix key gates the whole
231
+ // subtree, so the stricter rule has to survive — otherwise whichever page
232
+ // the document happened to list last silently decided the access rules.
233
+ const run = compileGeneratedMiddleware({
234
+ ...baseAuth,
235
+ pageProtection: {
236
+ PUBLIC_ISH: {
237
+ requiresAuth: true,
238
+ allowedRoles: [],
239
+ pageName: 'reports',
240
+ route: '/reports',
241
+ },
242
+ ADMIN_ONLY: {
243
+ requiresAuth: true,
244
+ allowedRoles: ['admin'],
245
+ pageName: 'reports-admin',
246
+ route: '/reports',
247
+ },
248
+ },
249
+ } as any)
250
+
251
+ expect((await run('/reports', { sessionUser: { id: 'u-1', role: 'user' } })).kind).toBe(
252
+ 'redirect'
253
+ )
254
+ expect(await run('/reports', { sessionUser: { id: 'u-2', role: 'admin' } })).toEqual({
255
+ kind: 'next',
256
+ })
257
+ })
258
+ })
@@ -1,4 +1,8 @@
1
1
  import { generateMiddlewareFile } from '../src/auth-generator'
2
+ import {
3
+ extractProtectedRoutes,
4
+ extractSelfGuardedRoutes,
5
+ } from './_helpers/run-generated-middleware'
2
6
 
3
7
  // Guarding the regression where a guest checkout buyer lands on
4
8
  // `/order-details/<order_number>` after Stripe redirect and the
@@ -51,8 +55,11 @@ describe('generateMiddlewareFile: row-owned self-guarded pages', () => {
51
55
 
52
56
  // /order-details must NOT appear in protectedRoutes — the
53
57
  // page-load SQL `user_id = userId OR user_id = anonymousUserId`
54
- // is the source of truth for ownership.
55
- expect(code).not.toContain('"/order-details"')
58
+ // is the source of truth for ownership. It DOES appear in
59
+ // selfGuardedRoutes, which is how the middleware recognises the
60
+ // requests it must let through when an ancestor route is protected.
61
+ expect(extractProtectedRoutes(code)).toEqual({})
62
+ expect(extractSelfGuardedRoutes(code)).toEqual(['/order-details/[order_number]'])
56
63
  })
57
64
 
58
65
  it('still protects a row-owned page that requires a specific role', () => {
@@ -74,8 +81,11 @@ describe('generateMiddlewareFile: row-owned self-guarded pages', () => {
74
81
  },
75
82
  } as any)
76
83
 
77
- expect(code).toContain('"/admin/orders/[id]"')
78
- expect(code).toContain('"admin"')
84
+ expect(extractProtectedRoutes(code)['/admin/orders/[id]']).toEqual({
85
+ requiresAuth: true,
86
+ allowedRoles: ['admin'],
87
+ })
88
+ expect(extractSelfGuardedRoutes(code)).toEqual([])
79
89
  })
80
90
 
81
91
  it('keeps non-row-owned pages protected as before', () => {
@@ -94,7 +104,11 @@ describe('generateMiddlewareFile: row-owned self-guarded pages', () => {
94
104
  },
95
105
  } as any)
96
106
 
97
- expect(code).toContain('"/orders-list"')
107
+ expect(extractProtectedRoutes(code)['/orders-list']).toEqual({
108
+ requiresAuth: true,
109
+ allowedRoles: [],
110
+ })
111
+ expect(extractSelfGuardedRoutes(code)).toEqual([])
98
112
  })
99
113
 
100
114
  it('honours folder-level role gating even when a child page is row-owned', () => {
@@ -124,8 +138,12 @@ describe('generateMiddlewareFile: row-owned self-guarded pages', () => {
124
138
  },
125
139
  } as any)
126
140
 
127
- expect(code).toContain('"/tickets/[id]"')
128
- expect(code).toContain('"staff"')
141
+ expect(extractProtectedRoutes(code)['/tickets/[id]']).toEqual({
142
+ requiresAuth: true,
143
+ allowedRoles: ['staff'],
144
+ })
145
+ // The folder's role gate is absolute, so the page loses its waiver.
146
+ expect(extractSelfGuardedRoutes(code)).toEqual([])
129
147
  })
130
148
 
131
149
  it('skips a row-owned page even when wrapped in a role-less folder', () => {
@@ -156,7 +174,8 @@ describe('generateMiddlewareFile: row-owned self-guarded pages', () => {
156
174
  },
157
175
  } as any)
158
176
 
159
- expect(code).not.toContain('"/order-details"')
177
+ expect(extractProtectedRoutes(code)).toEqual({})
178
+ expect(extractSelfGuardedRoutes(code)).toEqual(['/order-details'])
160
179
  })
161
180
 
162
181
  it('treats `rowOwnerColumn` as the sole signal — pages without it stay protected', () => {
@@ -176,6 +195,10 @@ describe('generateMiddlewareFile: row-owned self-guarded pages', () => {
176
195
  },
177
196
  } as any)
178
197
 
179
- expect(code).toContain('"/something"')
198
+ expect(extractProtectedRoutes(code)['/something']).toEqual({
199
+ requiresAuth: true,
200
+ allowedRoles: [],
201
+ })
202
+ expect(extractSelfGuardedRoutes(code)).toEqual([])
180
203
  })
181
204
  })
@@ -1 +1 @@
1
- {"version":3,"file":"auth-generator.d.ts","sourceRoot":"","sources":["../../src/auth-generator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAElB,cAAc,EACf,MAAM,4BAA4B,CAAA;AAkBnC,eAAO,MAAM,6BAA6B,mBACxB,cAAc,GAAG,IAAI,KACpC,OAAO,MAAM,EAAE,MAAM,CA0BvB,CAAA;AAmhBD,eAAO,MAAM,uBAAuB,SAC5B,kBAAkB,qBACL,OAAO,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,KAChD,MAsFF,CAAA;AAED,eAAO,MAAM,wBAAwB,QAAO,MA4B3C,CAAA;AAED,eAAO,MAAM,yBAAyB,QAAO,MAgC5C,CAAA;AAMD,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,eAAO,MAAM,uBAAuB,SAC5B,kBAAkB,YACf,mBAAmB,KAC3B,MA4IF,CAAA;AAsBD,eAAO,MAAM,sBAAsB,SAAU,kBAAkB,KAAG,MAiPjE,CAAA;AAED,eAAO,MAAM,8BAA8B,QAAO,MAqCjD,CAAA"}
1
+ {"version":3,"file":"auth-generator.d.ts","sourceRoot":"","sources":["../../src/auth-generator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAElB,cAAc,EACf,MAAM,4BAA4B,CAAA;AAkBnC,eAAO,MAAM,6BAA6B,mBACxB,cAAc,GAAG,IAAI,KACpC,OAAO,MAAM,EAAE,MAAM,CA0BvB,CAAA;AAmhBD,eAAO,MAAM,uBAAuB,SAC5B,kBAAkB,qBACL,OAAO,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,KAChD,MAsFF,CAAA;AAED,eAAO,MAAM,wBAAwB,QAAO,MA4B3C,CAAA;AAED,eAAO,MAAM,yBAAyB,QAAO,MAgC5C,CAAA;AAMD,MAAM,WAAW,mBAAmB;IAClC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,eAAO,MAAM,uBAAuB,SAC5B,kBAAkB,YACf,mBAAmB,KAC3B,MA4IF,CAAA;AAgDD,eAAO,MAAM,sBAAsB,SAAU,kBAAkB,KAAG,MA6UjE,CAAA;AAED,eAAO,MAAM,8BAA8B,QAAO,MAqCjD,CAAA"}