@cedarjs/cli 7.0.0 → 8.0.0-canary.3263

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 (168) hide show
  1. package/dist/commands/build/buildHandler.js +2 -1
  2. package/dist/commands/build.js +1 -1
  3. package/dist/commands/check.js +2 -1
  4. package/dist/commands/deploy/baremetal/baremetalHandler.js +1 -1
  5. package/dist/commands/deploy/baremetal.js +1 -1
  6. package/dist/commands/deploy/flightcontrolHandler.js +1 -1
  7. package/dist/commands/deploy/helpers/deployHandler.js +1 -1
  8. package/dist/commands/deploy/netlify.js +1 -1
  9. package/dist/commands/deploy/renderHandler.js +1 -1
  10. package/dist/commands/deploy/serverlessHandler.js +2 -1
  11. package/dist/commands/deploy/vercel.js +1 -1
  12. package/dist/commands/destroy/handlerHelpers.js +1 -1
  13. package/dist/commands/destroy/page/pageHandler.js +2 -1
  14. package/dist/commands/destroy/scaffold/scaffoldHandler.js +2 -1
  15. package/dist/commands/destroy/sdl/sdlHandler.js +2 -1
  16. package/dist/commands/dev/devHandler.js +3 -4
  17. package/dist/commands/dev/packageWatchCommands.js +8 -3
  18. package/dist/commands/dev.js +1 -1
  19. package/dist/commands/execHandler.js +2 -1
  20. package/dist/commands/experimental/live-queries/liveQueries.js +1 -1
  21. package/dist/commands/experimental/live-queries/liveQueriesHandler.js +3 -3
  22. package/dist/commands/experimental/setupInngest.js +1 -1
  23. package/dist/commands/experimental/setupInngestHandler.js +1 -1
  24. package/dist/commands/experimental/setupOpentelemetry.js +1 -1
  25. package/dist/commands/experimental/setupOpentelemetryHandler.js +2 -1
  26. package/dist/commands/experimental/setupReactCompiler.js +1 -1
  27. package/dist/commands/experimental/setupReactCompilerHandler.js +4 -4
  28. package/dist/commands/experimental/setupRsc.js +1 -1
  29. package/dist/commands/experimental/setupRscHandler.js +2 -1
  30. package/dist/commands/experimental/setupStreamingSsr.js +1 -1
  31. package/dist/commands/experimental/setupStreamingSsrHandler.js +3 -2
  32. package/dist/commands/generate/cell/cell.js +20 -0
  33. package/dist/commands/generate/cell/cellHandler.js +68 -20
  34. package/dist/commands/generate/cell/templates/cell.tsx.template +21 -2
  35. package/dist/commands/generate/cell/templates/cellFragment.tsx.template +43 -0
  36. package/dist/commands/generate/cell/templates/cellList.tsx.template +21 -2
  37. package/dist/commands/generate/cell/templates/storiesFragment.tsx.template +23 -0
  38. package/dist/commands/generate/cell/templates/test.js.template +1 -1
  39. package/dist/commands/generate/cell/templates/testFragment.js.template +30 -0
  40. package/dist/commands/generate/dataMigration/dataMigration.js +2 -1
  41. package/dist/commands/generate/dbAuth/dbAuthHandler.js +23 -3
  42. package/dist/commands/generate/dbAuth/templates/login.oauth.tsx.template +164 -0
  43. package/dist/commands/generate/dbAuth/templates/signup.oauth.tsx.template +147 -0
  44. package/dist/commands/generate/directive/directiveHandler.js +2 -1
  45. package/dist/commands/generate/function/functionHandler.js +2 -1
  46. package/dist/commands/generate/job/jobHandler.js +2 -1
  47. package/dist/commands/generate/model/modelHandler.js +2 -1
  48. package/dist/commands/generate/ogImage/ogImageHandler.js +2 -1
  49. package/dist/commands/generate/package/filesTask.js +9 -12
  50. package/dist/commands/generate/package/packageHandler.js +3 -4
  51. package/dist/commands/generate/page/pageHandler.js +2 -1
  52. package/dist/commands/generate/realtime/realtime.js +1 -1
  53. package/dist/commands/generate/realtime/realtimeHandler.js +2 -9
  54. package/dist/commands/generate/realtime/templates/subscriptions/blank/blank.ts.template +1 -1
  55. package/dist/commands/generate/scaffold/scaffoldHandler.js +10 -5
  56. package/dist/commands/generate/scaffold/templates/components/NameForm.tsx.template +3 -3
  57. package/dist/commands/generate/scaffold/templates/lib/formatters.test.tsx.template +11 -0
  58. package/dist/commands/generate/scaffold/templates/lib/formatters.tsx.template +13 -1
  59. package/dist/commands/generate/script/scriptHandler.js +2 -1
  60. package/dist/commands/generate/sdl/sdlHandler.js +2 -1
  61. package/dist/commands/generate/secret/secret.js +1 -1
  62. package/dist/commands/generate/service/serviceHandler.js +47 -38
  63. package/dist/commands/generate/service/templates/service.ts.template +22 -8
  64. package/dist/commands/generate/service/templates/test.ts.template +1 -1
  65. package/dist/commands/generate/yargsCommandHelpers.js +1 -1
  66. package/dist/commands/generate/yargsHandlerHelpers.js +2 -1
  67. package/dist/commands/generate.js +1 -1
  68. package/dist/commands/info.js +1 -1
  69. package/dist/commands/lint.js +1 -1
  70. package/dist/commands/prerenderHandler.js +11 -4
  71. package/dist/commands/prismaHandler.js +7 -2
  72. package/dist/commands/record/init.js +1 -1
  73. package/dist/commands/serve.js +2 -1
  74. package/dist/commands/setup/auth/auth.js +2 -4
  75. package/dist/commands/setup/cache/cache.js +1 -1
  76. package/dist/commands/setup/cache/cacheHandler.js +2 -1
  77. package/dist/commands/setup/database/postgres.js +1 -1
  78. package/dist/commands/setup/database/postgresHandler.js +3 -1
  79. package/dist/commands/setup/deploy/providers/baremetalHandler.js +2 -1
  80. package/dist/commands/setup/deploy/providers/coherence.js +1 -1
  81. package/dist/commands/setup/deploy/providers/coherenceHandler.js +7 -6
  82. package/dist/commands/setup/deploy/providers/flightcontrolHandler.js +2 -1
  83. package/dist/commands/setup/deploy/providers/netlifyHandler.js +2 -1
  84. package/dist/commands/setup/deploy/providers/renderHandler.js +2 -1
  85. package/dist/commands/setup/deploy/providers/serverlessHandler.js +2 -1
  86. package/dist/commands/setup/deploy/providers/universalDeployHandler.js +4 -6
  87. package/dist/commands/setup/deploy/providers/vercelHandler.js +2 -1
  88. package/dist/commands/setup/docker/docker.js +1 -1
  89. package/dist/commands/setup/docker/dockerHandler.js +2 -1
  90. package/dist/commands/setup/generator/generator.js +1 -1
  91. package/dist/commands/setup/generator/generatorHandler.js +1 -1
  92. package/dist/commands/setup/graphql/features/fragments/fragments.js +1 -1
  93. package/dist/commands/setup/graphql/features/fragments/fragmentsHandler.js +3 -5
  94. package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocuments.js +1 -1
  95. package/dist/commands/setup/graphql/features/trustedDocuments/trustedDocumentsHandler.js +2 -1
  96. package/dist/commands/setup/i18n/i18n.js +1 -1
  97. package/dist/commands/setup/i18n/i18nHandler.js +1 -1
  98. package/dist/commands/setup/jobs/jobs.js +1 -1
  99. package/dist/commands/setup/jobs/jobsHandler.js +2 -1
  100. package/dist/commands/setup/mailer/mailer.js +1 -1
  101. package/dist/commands/setup/mailer/mailerHandler.js +2 -1
  102. package/dist/commands/setup/middleware/ogImage/ogImage.js +1 -1
  103. package/dist/commands/setup/middleware/ogImage/ogImageHandler.js +2 -1
  104. package/dist/commands/setup/monitoring/sentry/sentry.js +1 -1
  105. package/dist/commands/setup/monitoring/sentry/sentryHandler.js +8 -7
  106. package/dist/commands/setup/neon/neon.js +1 -1
  107. package/dist/commands/setup/neon/neonHandler.js +5 -9
  108. package/dist/commands/setup/package/package.js +1 -1
  109. package/dist/commands/setup/package/packageHandler.js +4 -4
  110. package/dist/commands/setup/realtime/addRealtimeToGraphql.js +1 -1
  111. package/dist/commands/setup/realtime/realtime.js +1 -1
  112. package/dist/commands/setup/realtime/realtimeHandler.js +4 -16
  113. package/dist/commands/setup/realtime/templates/subscriptions/countdown/countdown.ts.template +1 -1
  114. package/dist/commands/setup/realtime/templates/subscriptions/newMessage/newMessage.ts.template +1 -1
  115. package/dist/commands/setup/server-file/serverFile.js +1 -1
  116. package/dist/commands/setup/server-file/serverFileHandler.js +2 -1
  117. package/dist/commands/setup/tenancy/authCodemod.js +66 -0
  118. package/dist/commands/setup/tenancy/dbCodemod.js +64 -0
  119. package/dist/commands/setup/tenancy/graphqlCodemod.js +88 -0
  120. package/dist/commands/setup/tenancy/preflight.js +49 -0
  121. package/dist/commands/setup/tenancy/schemaPrisma.js +81 -0
  122. package/dist/commands/setup/tenancy/signupCodemod.js +153 -0
  123. package/dist/commands/setup/tenancy/templates/InvitePage.tsx.template +63 -0
  124. package/dist/commands/setup/tenancy/templates/OrgScope.tsx.template +40 -0
  125. package/dist/commands/setup/tenancy/templates/ensureDefaultOrganizations.dataMigration.ts.template +39 -0
  126. package/dist/commands/setup/tenancy/templates/organizations.scenarios.ts.template +59 -0
  127. package/dist/commands/setup/tenancy/templates/organizations.sdl.ts.template +89 -0
  128. package/dist/commands/setup/tenancy/templates/organizations.test.ts.template +389 -0
  129. package/dist/commands/setup/tenancy/templates/organizations.ts.template +394 -0
  130. package/dist/commands/setup/tenancy/templates/requireMembership.test.ts.template +35 -0
  131. package/dist/commands/setup/tenancy/templates/requireMembership.ts.template +25 -0
  132. package/dist/commands/setup/tenancy/tenancy.js +30 -0
  133. package/dist/commands/setup/tenancy/tenancyHandler.js +425 -0
  134. package/dist/commands/setup/ui/libraries/chakra-uiHandler.js +2 -1
  135. package/dist/commands/setup/ui/libraries/mantineHandler.js +2 -1
  136. package/dist/commands/setup/ui/libraries/tailwindcssHandler.js +2 -1
  137. package/dist/commands/setup/uploads/schemaPrisma.js +41 -0
  138. package/dist/commands/setup/uploads/serverFile.js +151 -0
  139. package/dist/commands/setup/uploads/templates/requireUploadToken.ts.template +15 -0
  140. package/dist/commands/setup/uploads/templates/srcLibUploads.ts.template +23 -24
  141. package/dist/commands/setup/uploads/templates/uploads.sdl.ts.template +79 -0
  142. package/dist/commands/setup/uploads/templates/uploads.ts.template +89 -0
  143. package/dist/commands/setup/uploads/templates/withDataUri.ts.template +14 -0
  144. package/dist/commands/setup/uploads/templates/withSignedUrl.ts.template +16 -0
  145. package/dist/commands/setup/uploads/uploads.js +49 -7
  146. package/dist/commands/setup/uploads/uploadsHandler.js +256 -69
  147. package/dist/commands/setup.js +2 -1
  148. package/dist/commands/studio.js +1 -1
  149. package/dist/commands/test/testHandler.js +25 -71
  150. package/dist/commands/test/viteVersionCheck.js +75 -0
  151. package/dist/commands/test.js +10 -18
  152. package/dist/commands/type-checkHandler.js +1 -1
  153. package/dist/commands/upgrade/preUpgradeScripts.js +3 -3
  154. package/dist/commands/upgrade/upgrade.js +1 -1
  155. package/dist/commands/upgrade/upgradeHandler.js +2 -1
  156. package/dist/index.js +5 -8
  157. package/dist/lib/exit.js +1 -1
  158. package/dist/lib/index.js +6 -25
  159. package/dist/lib/loadEnvFiles.js +1 -1
  160. package/dist/lib/plugin.js +1 -1
  161. package/dist/lib/test.js +1 -1
  162. package/dist/lib/updateCheck.js +4 -4
  163. package/dist/middleware/checkNodeVersion.js +4 -4
  164. package/package.json +18 -18
  165. package/dist/commands/setup/uploads/dbCodemod.js +0 -28
  166. package/dist/commands/setup/uploads/templates/signedUrl.ts.template +0 -21
  167. package/dist/commands/test/testHandlerEsm.js +0 -109
  168. package/dist/commands/testEsm.js +0 -41
@@ -0,0 +1,389 @@
1
+ import { setContext } from '@cedarjs/context'
2
+
3
+ import { db } from 'src/lib/db'
4
+
5
+ import {
6
+ myMemberships,
7
+ organization,
8
+ inviteMember,
9
+ acceptInvitation,
10
+ removeMember,
11
+ ensureDefaultOrganization,
12
+ } from './organizations.js'
13
+ import type { StandardScenario } from './organizations.scenarios.js'
14
+
15
+ // Generated boilerplate tests do not account for all circumstances and can
16
+ // fail without adjustments, e.g. a `User` model shaped by a different auth
17
+ // provider than the dbAuth one this scenario assumes. Please refer to the
18
+ // CedarJS Testing Docs:
19
+ // https://cedarjs.com/docs/testing#testing-services
20
+
21
+ /**
22
+ * `context.currentUser` is typed from this app's `getCurrentUser`, which
23
+ * selects more fields than these tests need.
24
+ */
25
+ const asCurrentUser = (user: { id: string; memberships?: unknown[] }) =>
26
+ user as unknown as typeof context.currentUser
27
+
28
+ /**
29
+ * Sets `context.currentUser` and `context.currentOrg` the way the GraphQL
30
+ * `context` function does for an authenticated, in-organization request.
31
+ */
32
+ const asMember = (
33
+ scenario: StandardScenario,
34
+ membership: { id: string; userId: string | null; role: string },
35
+ ) => {
36
+ setContext({
37
+ currentUser: asCurrentUser({ id: membership.userId! }),
38
+ currentOrg: {
39
+ id: scenario.organization.acme.id,
40
+ slug: scenario.organization.acme.slug,
41
+ role: membership.role,
42
+ membershipId: membership.id,
43
+ },
44
+ })
45
+ }
46
+
47
+ describe('organizations service', () => {
48
+ scenario(
49
+ "myMemberships returns the signed-in user's memberships",
50
+ async (scenario: StandardScenario) => {
51
+ setContext({
52
+ currentUser: asCurrentUser({ id: scenario.membership.owner.userId! }),
53
+ })
54
+
55
+ const result = await myMemberships()
56
+
57
+ expect(result).toHaveLength(1)
58
+ expect(result[0]).toMatchObject({ id: scenario.membership.owner.id })
59
+ },
60
+ )
61
+
62
+ scenario(
63
+ 'organization returns the current organization',
64
+ async (scenario: StandardScenario) => {
65
+ asMember(scenario, scenario.membership.owner)
66
+
67
+ const result = await organization({ id: scenario.organization.acme.id })
68
+
69
+ expect(result?.id).toEqual(scenario.organization.acme.id)
70
+ },
71
+ )
72
+
73
+ scenario(
74
+ 'organization refuses an id other than the current organization',
75
+ async (scenario: StandardScenario) => {
76
+ asMember(scenario, scenario.membership.owner)
77
+
78
+ await expect(organization({ id: 'some-other-org' })).rejects.toThrow()
79
+ },
80
+ )
81
+
82
+ describe('inviteMember', () => {
83
+ scenario(
84
+ 'an owner can invite a member',
85
+ async (scenario: StandardScenario) => {
86
+ asMember(scenario, scenario.membership.owner)
87
+
88
+ const result = await inviteMember({
89
+ input: { email: 'new-member@example.com', role: 'member' },
90
+ })
91
+
92
+ expect(result.invitationToken).toBeTruthy()
93
+
94
+ // `Invitation.membership` is resolver-backed like any relation
95
+ // field, so its generated type allows a promise; read the row back
96
+ // from `db` instead of asserting on `result.membership` directly.
97
+ const created = await db.membership.findFirst({
98
+ where: { invitedById: scenario.membership.owner.id },
99
+ })
100
+
101
+ expect(created?.role).toEqual('member')
102
+ expect(created?.invitationToken).toEqual(result.invitationToken)
103
+ },
104
+ )
105
+
106
+ scenario(
107
+ 'an admin cannot invite an owner',
108
+ async (scenario: StandardScenario) => {
109
+ asMember(scenario, scenario.membership.admin)
110
+
111
+ await expect(
112
+ inviteMember({
113
+ input: { email: 'new-owner@example.com', role: 'owner' },
114
+ }),
115
+ ).rejects.toThrow()
116
+ },
117
+ )
118
+
119
+ scenario(
120
+ 'an unknown role is rejected',
121
+ async (scenario: StandardScenario) => {
122
+ asMember(scenario, scenario.membership.owner)
123
+
124
+ await expect(
125
+ inviteMember({
126
+ input: { email: 'typo@example.com', role: 'superadmin' },
127
+ }),
128
+ ).rejects.toThrow()
129
+ },
130
+ )
131
+ })
132
+
133
+ describe('removeMember', () => {
134
+ scenario(
135
+ 'an owner can remove an admin',
136
+ async (scenario: StandardScenario) => {
137
+ asMember(scenario, scenario.membership.owner)
138
+
139
+ await removeMember({ membershipId: scenario.membership.admin.id })
140
+
141
+ const remaining = await db.membership.findUnique({
142
+ where: { id: scenario.membership.admin.id },
143
+ })
144
+
145
+ expect(remaining).toBeNull()
146
+ },
147
+ )
148
+
149
+ scenario(
150
+ 'an admin cannot remove an owner',
151
+ async (scenario: StandardScenario) => {
152
+ asMember(scenario, scenario.membership.admin)
153
+
154
+ await expect(
155
+ removeMember({ membershipId: scenario.membership.owner.id }),
156
+ ).rejects.toThrow()
157
+ },
158
+ )
159
+
160
+ scenario(
161
+ "the organization's last owner cannot be removed",
162
+ async (scenario: StandardScenario) => {
163
+ asMember(scenario, scenario.membership.owner)
164
+
165
+ await expect(
166
+ removeMember({ membershipId: scenario.membership.owner.id }),
167
+ ).rejects.toThrow()
168
+ },
169
+ )
170
+
171
+ scenario(
172
+ 'an unclaimed owner invitation does not count as the remaining owner',
173
+ async (scenario: StandardScenario) => {
174
+ asMember(scenario, scenario.membership.owner)
175
+
176
+ // Nobody has accepted this one yet, so it leaves the organization
177
+ // with no owner who can administer it.
178
+ await db.membership.create({
179
+ data: {
180
+ role: 'owner',
181
+ organizationId: scenario.organization.acme.id,
182
+ invitationToken: 'pending-owner-token',
183
+ },
184
+ })
185
+
186
+ await expect(
187
+ removeMember({ membershipId: scenario.membership.owner.id }),
188
+ ).rejects.toThrow()
189
+ },
190
+ )
191
+
192
+ scenario(
193
+ 'an owner can be removed once another owner has accepted',
194
+ async (scenario: StandardScenario) => {
195
+ asMember(scenario, scenario.membership.owner)
196
+
197
+ const second = await db.user.create({
198
+ data: {
199
+ email: 'second-owner@example.com',
200
+ hashedPassword: 'x',
201
+ salt: 'x',
202
+ fullName: 'Second Owner',
203
+ },
204
+ })
205
+ await db.membership.create({
206
+ data: {
207
+ role: 'owner',
208
+ organizationId: scenario.organization.acme.id,
209
+ userId: second.id,
210
+ },
211
+ })
212
+
213
+ const result = await removeMember({
214
+ membershipId: scenario.membership.owner.id,
215
+ })
216
+
217
+ expect(result.id).toEqual(scenario.membership.owner.id)
218
+ await expect(
219
+ db.membership.findUnique({
220
+ where: { id: scenario.membership.owner.id },
221
+ }),
222
+ ).resolves.toBeNull()
223
+ },
224
+ )
225
+ })
226
+
227
+ describe('acceptInvitation', () => {
228
+ scenario(
229
+ 'attaches a pending invitation to the signed-in user',
230
+ async (scenario: StandardScenario) => {
231
+ const invitee = await db.user.create({
232
+ data: {
233
+ email: 'invitee@example.com',
234
+ hashedPassword: 'x',
235
+ salt: 'x',
236
+ fullName: 'Invitee',
237
+ },
238
+ })
239
+
240
+ setContext({ currentUser: asCurrentUser({ id: invitee.id }) })
241
+
242
+ const result = await acceptInvitation({
243
+ token: scenario.membership.pending.invitationToken!,
244
+ })
245
+
246
+ expect(result.id).toEqual(scenario.organization.acme.id)
247
+ },
248
+ )
249
+
250
+ scenario(
251
+ 'a repeat call from the same user still succeeds',
252
+ async (scenario: StandardScenario) => {
253
+ const invitee = await db.user.create({
254
+ data: {
255
+ email: 'invitee@example.com',
256
+ hashedPassword: 'x',
257
+ salt: 'x',
258
+ fullName: 'Invitee',
259
+ },
260
+ })
261
+
262
+ setContext({ currentUser: asCurrentUser({ id: invitee.id }) })
263
+
264
+ const token = scenario.membership.pending.invitationToken!
265
+
266
+ const first = await acceptInvitation({ token })
267
+ const second = await acceptInvitation({ token })
268
+
269
+ expect(second.id).toEqual(first.id)
270
+ },
271
+ )
272
+
273
+ scenario(
274
+ 'a second user cannot claim an already-claimed token',
275
+ async (scenario: StandardScenario) => {
276
+ const firstUser = await db.user.create({
277
+ data: {
278
+ email: 'first@example.com',
279
+ hashedPassword: 'x',
280
+ salt: 'x',
281
+ fullName: 'First User',
282
+ },
283
+ })
284
+ const secondUser = await db.user.create({
285
+ data: {
286
+ email: 'second@example.com',
287
+ hashedPassword: 'x',
288
+ salt: 'x',
289
+ fullName: 'Second User',
290
+ },
291
+ })
292
+
293
+ const token = scenario.membership.pending.invitationToken!
294
+
295
+ setContext({ currentUser: asCurrentUser({ id: firstUser.id }) })
296
+ await acceptInvitation({ token })
297
+
298
+ setContext({ currentUser: asCurrentUser({ id: secondUser.id }) })
299
+ await expect(acceptInvitation({ token })).rejects.toThrow()
300
+ },
301
+ )
302
+
303
+ scenario(
304
+ 'clears the invitation token once claimed',
305
+ async (scenario: StandardScenario) => {
306
+ const invitee = await db.user.create({
307
+ data: {
308
+ email: 'invitee@example.com',
309
+ hashedPassword: 'x',
310
+ salt: 'x',
311
+ fullName: 'Invitee',
312
+ },
313
+ })
314
+
315
+ const token = scenario.membership.pending.invitationToken!
316
+
317
+ setContext({ currentUser: asCurrentUser({ id: invitee.id }) })
318
+ await acceptInvitation({ token })
319
+
320
+ const claimed = await db.membership.findUnique({
321
+ where: { id: scenario.membership.pending.id },
322
+ })
323
+
324
+ expect(claimed?.userId).toEqual(invitee.id)
325
+ expect(claimed?.invitationToken).not.toEqual(token)
326
+ },
327
+ )
328
+
329
+ it('rejects a token that never matched a pending invitation', async () => {
330
+ setContext({ currentUser: asCurrentUser({ id: 'some-user-id' }) })
331
+
332
+ await expect(
333
+ acceptInvitation({ token: 'not-a-real-token' }),
334
+ ).rejects.toThrow()
335
+ })
336
+ })
337
+
338
+ it('ensureDefaultOrganization is idempotent when the user already has memberships', async () => {
339
+ const existingMemberships = [
340
+ { id: 'membership-1', organizationId: 'org-1', role: 'owner' },
341
+ ]
342
+
343
+ const result = await ensureDefaultOrganization({
344
+ currentUser: { id: 'user-1', memberships: existingMemberships },
345
+ })
346
+
347
+ expect(result).toBe(existingMemberships)
348
+ })
349
+
350
+ scenario(
351
+ 'ensureDefaultOrganization attaches a pending invitation',
352
+ async (scenario: StandardScenario) => {
353
+ const invitee = await db.user.create({
354
+ data: {
355
+ email: 'invitee@example.com',
356
+ hashedPassword: 'x',
357
+ salt: 'x',
358
+ fullName: 'Invitee',
359
+ },
360
+ })
361
+
362
+ const result = await ensureDefaultOrganization({
363
+ currentUser: { id: invitee.id, memberships: [] },
364
+ invitationToken: scenario.membership.pending.invitationToken,
365
+ })
366
+
367
+ expect(result).toHaveLength(1)
368
+ expect(result[0].organizationId).toEqual(scenario.organization.acme.id)
369
+ },
370
+ )
371
+
372
+ it('ensureDefaultOrganization creates a new organization when there is no invitation', async () => {
373
+ const newUser = await db.user.create({
374
+ data: {
375
+ email: 'brand-new@example.com',
376
+ hashedPassword: 'x',
377
+ salt: 'x',
378
+ fullName: 'Brand New',
379
+ },
380
+ })
381
+
382
+ const result = await ensureDefaultOrganization({
383
+ currentUser: { id: newUser.id, memberships: [] },
384
+ })
385
+
386
+ expect(result).toHaveLength(1)
387
+ expect(result[0].role).toEqual('owner')
388
+ })
389
+ })