@shipfox/api-workspaces 2.0.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.
Files changed (284) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +97 -0
  5. package/LICENSE +21 -0
  6. package/README.md +87 -0
  7. package/dist/config.d.ts +12 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +58 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/core/entities/index.d.ts +4 -0
  12. package/dist/core/entities/index.d.ts.map +1 -0
  13. package/dist/core/entities/index.js +3 -0
  14. package/dist/core/entities/index.js.map +1 -0
  15. package/dist/core/entities/invitation.d.ts +14 -0
  16. package/dist/core/entities/invitation.d.ts.map +1 -0
  17. package/dist/core/entities/invitation.js +3 -0
  18. package/dist/core/entities/invitation.js.map +1 -0
  19. package/dist/core/entities/membership.d.ts +10 -0
  20. package/dist/core/entities/membership.d.ts.map +1 -0
  21. package/dist/core/entities/membership.js +3 -0
  22. package/dist/core/entities/membership.js.map +1 -0
  23. package/dist/core/entities/workspace.d.ts +10 -0
  24. package/dist/core/entities/workspace.d.ts.map +1 -0
  25. package/dist/core/entities/workspace.js +3 -0
  26. package/dist/core/entities/workspace.js.map +1 -0
  27. package/dist/core/errors.d.ts +52 -0
  28. package/dist/core/errors.d.ts.map +1 -0
  29. package/dist/core/errors.js +104 -0
  30. package/dist/core/errors.js.map +1 -0
  31. package/dist/core/index.d.ts +8 -0
  32. package/dist/core/index.d.ts.map +1 -0
  33. package/dist/core/index.js +6 -0
  34. package/dist/core/index.js.map +1 -0
  35. package/dist/core/invitations.d.ts +51 -0
  36. package/dist/core/invitations.d.ts.map +1 -0
  37. package/dist/core/invitations.js +136 -0
  38. package/dist/core/invitations.js.map +1 -0
  39. package/dist/core/memberships.d.ts +9 -0
  40. package/dist/core/memberships.d.ts.map +1 -0
  41. package/dist/core/memberships.js +13 -0
  42. package/dist/core/memberships.js.map +1 -0
  43. package/dist/core/workspaces.d.ts +40 -0
  44. package/dist/core/workspaces.d.ts.map +1 -0
  45. package/dist/core/workspaces.js +85 -0
  46. package/dist/core/workspaces.js.map +1 -0
  47. package/dist/db/db.d.ts +1253 -0
  48. package/dist/db/db.d.ts.map +1 -0
  49. package/dist/db/db.js +24 -0
  50. package/dist/db/db.js.map +1 -0
  51. package/dist/db/index.d.ts +10 -0
  52. package/dist/db/index.d.ts.map +1 -0
  53. package/dist/db/index.js +10 -0
  54. package/dist/db/index.js.map +1 -0
  55. package/dist/db/invitations.d.ts +47 -0
  56. package/dist/db/invitations.d.ts.map +1 -0
  57. package/dist/db/invitations.js +107 -0
  58. package/dist/db/invitations.js.map +1 -0
  59. package/dist/db/memberships.d.ts +44 -0
  60. package/dist/db/memberships.d.ts.map +1 -0
  61. package/dist/db/memberships.js +75 -0
  62. package/dist/db/memberships.js.map +1 -0
  63. package/dist/db/schema/common.d.ts +2 -0
  64. package/dist/db/schema/common.d.ts.map +1 -0
  65. package/dist/db/schema/common.js +4 -0
  66. package/dist/db/schema/common.js.map +1 -0
  67. package/dist/db/schema/invitations.d.ts +199 -0
  68. package/dist/db/schema/invitations.d.ts.map +1 -0
  69. package/dist/db/schema/invitations.js +47 -0
  70. package/dist/db/schema/invitations.js.map +1 -0
  71. package/dist/db/schema/memberships.d.ts +131 -0
  72. package/dist/db/schema/memberships.d.ts.map +1 -0
  73. package/dist/db/schema/memberships.js +35 -0
  74. package/dist/db/schema/memberships.js.map +1 -0
  75. package/dist/db/schema/outbox.d.ts +195 -0
  76. package/dist/db/schema/outbox.d.ts.map +1 -0
  77. package/dist/db/schema/outbox.js +5 -0
  78. package/dist/db/schema/outbox.js.map +1 -0
  79. package/dist/db/schema/workspaces.d.ts +115 -0
  80. package/dist/db/schema/workspaces.d.ts.map +1 -0
  81. package/dist/db/schema/workspaces.js +32 -0
  82. package/dist/db/schema/workspaces.js.map +1 -0
  83. package/dist/db/workspaces.d.ts +20 -0
  84. package/dist/db/workspaces.d.ts.map +1 -0
  85. package/dist/db/workspaces.js +53 -0
  86. package/dist/db/workspaces.js.map +1 -0
  87. package/dist/index.d.ts +13 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +40 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/metrics/index.d.ts +2 -0
  92. package/dist/metrics/index.d.ts.map +1 -0
  93. package/dist/metrics/index.js +3 -0
  94. package/dist/metrics/index.js.map +1 -0
  95. package/dist/metrics/instance.d.ts +8 -0
  96. package/dist/metrics/instance.d.ts.map +1 -0
  97. package/dist/metrics/instance.js +41 -0
  98. package/dist/metrics/instance.js.map +1 -0
  99. package/dist/metrics/service.d.ts +2 -0
  100. package/dist/metrics/service.d.ts.map +1 -0
  101. package/dist/metrics/service.js +26 -0
  102. package/dist/metrics/service.js.map +1 -0
  103. package/dist/presentation/dto/index.d.ts +4 -0
  104. package/dist/presentation/dto/index.d.ts.map +1 -0
  105. package/dist/presentation/dto/index.js +5 -0
  106. package/dist/presentation/dto/index.js.map +1 -0
  107. package/dist/presentation/dto/invitation.d.ts +4 -0
  108. package/dist/presentation/dto/invitation.d.ts.map +1 -0
  109. package/dist/presentation/dto/invitation.js +15 -0
  110. package/dist/presentation/dto/invitation.js.map +1 -0
  111. package/dist/presentation/dto/membership.d.ts +5 -0
  112. package/dist/presentation/dto/membership.d.ts.map +1 -0
  113. package/dist/presentation/dto/membership.js +23 -0
  114. package/dist/presentation/dto/membership.js.map +1 -0
  115. package/dist/presentation/dto/workspace.d.ts +4 -0
  116. package/dist/presentation/dto/workspace.d.ts.map +1 -0
  117. package/dist/presentation/dto/workspace.js +12 -0
  118. package/dist/presentation/dto/workspace.js.map +1 -0
  119. package/dist/presentation/e2eRoutes/create-invitation.d.ts +2 -0
  120. package/dist/presentation/e2eRoutes/create-invitation.d.ts.map +1 -0
  121. package/dist/presentation/e2eRoutes/create-invitation.js +39 -0
  122. package/dist/presentation/e2eRoutes/create-invitation.js.map +1 -0
  123. package/dist/presentation/e2eRoutes/create-workspace.d.ts +2 -0
  124. package/dist/presentation/e2eRoutes/create-workspace.d.ts.map +1 -0
  125. package/dist/presentation/e2eRoutes/create-workspace.js +27 -0
  126. package/dist/presentation/e2eRoutes/create-workspace.js.map +1 -0
  127. package/dist/presentation/e2eRoutes/index.d.ts +3 -0
  128. package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
  129. package/dist/presentation/e2eRoutes/index.js +11 -0
  130. package/dist/presentation/e2eRoutes/index.js.map +1 -0
  131. package/dist/presentation/index.d.ts +2 -0
  132. package/dist/presentation/index.d.ts.map +1 -0
  133. package/dist/presentation/index.js +3 -0
  134. package/dist/presentation/index.js.map +1 -0
  135. package/dist/presentation/routes/index.d.ts +3 -0
  136. package/dist/presentation/routes/index.d.ts.map +1 -0
  137. package/dist/presentation/routes/index.js +23 -0
  138. package/dist/presentation/routes/index.js.map +1 -0
  139. package/dist/presentation/routes/invitations/accept.d.ts +2 -0
  140. package/dist/presentation/routes/invitations/accept.d.ts.map +1 -0
  141. package/dist/presentation/routes/invitations/accept.js +67 -0
  142. package/dist/presentation/routes/invitations/accept.js.map +1 -0
  143. package/dist/presentation/routes/invitations/create.d.ts +2 -0
  144. package/dist/presentation/routes/invitations/create.d.ts.map +1 -0
  145. package/dist/presentation/routes/invitations/create.js +66 -0
  146. package/dist/presentation/routes/invitations/create.js.map +1 -0
  147. package/dist/presentation/routes/invitations/index.d.ts +4 -0
  148. package/dist/presentation/routes/invitations/index.d.ts.map +1 -0
  149. package/dist/presentation/routes/invitations/index.js +19 -0
  150. package/dist/presentation/routes/invitations/index.js.map +1 -0
  151. package/dist/presentation/routes/invitations/list.d.ts +2 -0
  152. package/dist/presentation/routes/invitations/list.d.ts.map +1 -0
  153. package/dist/presentation/routes/invitations/list.js +58 -0
  154. package/dist/presentation/routes/invitations/list.js.map +1 -0
  155. package/dist/presentation/routes/invitations/preview.d.ts +2 -0
  156. package/dist/presentation/routes/invitations/preview.d.ts.map +1 -0
  157. package/dist/presentation/routes/invitations/preview.js +47 -0
  158. package/dist/presentation/routes/invitations/preview.js.map +1 -0
  159. package/dist/presentation/routes/invitations/revoke.d.ts +2 -0
  160. package/dist/presentation/routes/invitations/revoke.d.ts.map +1 -0
  161. package/dist/presentation/routes/invitations/revoke.js +66 -0
  162. package/dist/presentation/routes/invitations/revoke.js.map +1 -0
  163. package/dist/presentation/routes/members/index.d.ts +3 -0
  164. package/dist/presentation/routes/members/index.d.ts.map +1 -0
  165. package/dist/presentation/routes/members/index.js +8 -0
  166. package/dist/presentation/routes/members/index.js.map +1 -0
  167. package/dist/presentation/routes/members/list.d.ts +2 -0
  168. package/dist/presentation/routes/members/list.d.ts.map +1 -0
  169. package/dist/presentation/routes/members/list.js +58 -0
  170. package/dist/presentation/routes/members/list.js.map +1 -0
  171. package/dist/presentation/routes/members/remove.d.ts +2 -0
  172. package/dist/presentation/routes/members/remove.d.ts.map +1 -0
  173. package/dist/presentation/routes/members/remove.js +71 -0
  174. package/dist/presentation/routes/members/remove.js.map +1 -0
  175. package/dist/presentation/routes/workspaces/create.d.ts +2 -0
  176. package/dist/presentation/routes/workspaces/create.d.ts.map +1 -0
  177. package/dist/presentation/routes/workspaces/create.js +36 -0
  178. package/dist/presentation/routes/workspaces/create.js.map +1 -0
  179. package/dist/presentation/routes/workspaces/index.d.ts +3 -0
  180. package/dist/presentation/routes/workspaces/index.d.ts.map +1 -0
  181. package/dist/presentation/routes/workspaces/index.js +4 -0
  182. package/dist/presentation/routes/workspaces/index.js.map +1 -0
  183. package/dist/presentation/routes/workspaces/list.d.ts +2 -0
  184. package/dist/presentation/routes/workspaces/list.d.ts.map +1 -0
  185. package/dist/presentation/routes/workspaces/list.js +32 -0
  186. package/dist/presentation/routes/workspaces/list.js.map +1 -0
  187. package/dist/presentation/subscribers/index.d.ts +2 -0
  188. package/dist/presentation/subscribers/index.d.ts.map +1 -0
  189. package/dist/presentation/subscribers/index.js +3 -0
  190. package/dist/presentation/subscribers/index.js.map +1 -0
  191. package/dist/presentation/subscribers/on-invitation-send-requested.d.ts +3 -0
  192. package/dist/presentation/subscribers/on-invitation-send-requested.d.ts.map +1 -0
  193. package/dist/presentation/subscribers/on-invitation-send-requested.js +16 -0
  194. package/dist/presentation/subscribers/on-invitation-send-requested.js.map +1 -0
  195. package/dist/tsconfig.test.tsbuildinfo +1 -0
  196. package/drizzle/0000_initial.sql +9 -0
  197. package/drizzle/0001_memberships_invitations.sql +30 -0
  198. package/drizzle/0002_membership_user_snapshots.sql +40 -0
  199. package/drizzle/0003_outgoing_enchantress.sql +16 -0
  200. package/drizzle/meta/0000_snapshot.json +80 -0
  201. package/drizzle/meta/0001_snapshot.json +705 -0
  202. package/drizzle/meta/0003_snapshot.json +442 -0
  203. package/drizzle/meta/_journal.json +34 -0
  204. package/drizzle.config.ts +7 -0
  205. package/package.json +71 -0
  206. package/src/config.ts +51 -0
  207. package/src/core/entities/index.ts +3 -0
  208. package/src/core/entities/invitation.ts +13 -0
  209. package/src/core/entities/membership.ts +9 -0
  210. package/src/core/entities/workspace.ts +10 -0
  211. package/src/core/errors.ts +118 -0
  212. package/src/core/index.ts +35 -0
  213. package/src/core/invitations.test.ts +251 -0
  214. package/src/core/invitations.ts +191 -0
  215. package/src/core/memberships.test.ts +77 -0
  216. package/src/core/memberships.ts +17 -0
  217. package/src/core/workspaces.test.ts +72 -0
  218. package/src/core/workspaces.ts +126 -0
  219. package/src/db/db.ts +24 -0
  220. package/src/db/index.ts +43 -0
  221. package/src/db/invitations.test.ts +204 -0
  222. package/src/db/invitations.ts +235 -0
  223. package/src/db/memberships.test.ts +107 -0
  224. package/src/db/memberships.ts +139 -0
  225. package/src/db/schema/common.ts +5 -0
  226. package/src/db/schema/invitations.ts +47 -0
  227. package/src/db/schema/memberships.ts +39 -0
  228. package/src/db/schema/outbox.ts +4 -0
  229. package/src/db/schema/workspaces.ts +33 -0
  230. package/src/db/workspaces.test.ts +114 -0
  231. package/src/db/workspaces.ts +81 -0
  232. package/src/index.test.ts +25 -0
  233. package/src/index.ts +43 -0
  234. package/src/metrics/index.ts +1 -0
  235. package/src/metrics/instance.test.ts +74 -0
  236. package/src/metrics/instance.ts +57 -0
  237. package/src/metrics/service.ts +26 -0
  238. package/src/presentation/dto/index.ts +3 -0
  239. package/src/presentation/dto/invitation.ts +16 -0
  240. package/src/presentation/dto/membership.ts +27 -0
  241. package/src/presentation/dto/workspace.ts +13 -0
  242. package/src/presentation/e2eRoutes/create-invitation.ts +44 -0
  243. package/src/presentation/e2eRoutes/create-workspace.ts +30 -0
  244. package/src/presentation/e2eRoutes/index.ts +8 -0
  245. package/src/presentation/index.ts +1 -0
  246. package/src/presentation/routes/index.ts +23 -0
  247. package/src/presentation/routes/invitations/accept.test.ts +154 -0
  248. package/src/presentation/routes/invitations/accept.ts +70 -0
  249. package/src/presentation/routes/invitations/create.test.ts +99 -0
  250. package/src/presentation/routes/invitations/create.ts +60 -0
  251. package/src/presentation/routes/invitations/index.ts +17 -0
  252. package/src/presentation/routes/invitations/list.test.ts +94 -0
  253. package/src/presentation/routes/invitations/list.ts +53 -0
  254. package/src/presentation/routes/invitations/preview.test.ts +140 -0
  255. package/src/presentation/routes/invitations/preview.ts +46 -0
  256. package/src/presentation/routes/invitations/revoke.test.ts +99 -0
  257. package/src/presentation/routes/invitations/revoke.ts +64 -0
  258. package/src/presentation/routes/members/index.ts +5 -0
  259. package/src/presentation/routes/members/list.test.ts +95 -0
  260. package/src/presentation/routes/members/list.ts +53 -0
  261. package/src/presentation/routes/members/remove.test.ts +110 -0
  262. package/src/presentation/routes/members/remove.ts +66 -0
  263. package/src/presentation/routes/workspaces/create.test.ts +87 -0
  264. package/src/presentation/routes/workspaces/create.ts +36 -0
  265. package/src/presentation/routes/workspaces/index.ts +2 -0
  266. package/src/presentation/routes/workspaces/list.test.ts +56 -0
  267. package/src/presentation/routes/workspaces/list.ts +29 -0
  268. package/src/presentation/subscribers/index.ts +1 -0
  269. package/src/presentation/subscribers/invitation-send-requested.test.ts +64 -0
  270. package/src/presentation/subscribers/on-invitation-send-requested.ts +15 -0
  271. package/test/env.ts +8 -0
  272. package/test/factories/index.ts +3 -0
  273. package/test/factories/membership.ts +24 -0
  274. package/test/factories/user.ts +15 -0
  275. package/test/factories/workspace.ts +18 -0
  276. package/test/globalSetup.ts +15 -0
  277. package/test/index.ts +3 -0
  278. package/test/routes.ts +255 -0
  279. package/test/setup.ts +17 -0
  280. package/tsconfig.build.json +9 -0
  281. package/tsconfig.build.tsbuildinfo +1 -0
  282. package/tsconfig.json +3 -0
  283. package/tsconfig.test.json +8 -0
  284. package/vitest.config.ts +11 -0
package/test/routes.ts ADDED
@@ -0,0 +1,255 @@
1
+ import type {OutgoingHttpHeaders} from 'node:http';
2
+ import {AUTH_USER, buildUserContext, setUserContext} from '@shipfox/api-auth-context';
3
+ import {WORKSPACES_INVITATION_SEND_REQUESTED} from '@shipfox/api-workspaces-dto';
4
+ import type {AuthMethod} from '@shipfox/node-fastify';
5
+ import {createApp, type FastifyInstance} from '@shipfox/node-fastify';
6
+ import type {Mailer, MailMessage} from '@shipfox/node-mailer';
7
+ import {hashOpaqueToken} from '@shipfox/node-tokens';
8
+ import {and, desc, eq, sql} from 'drizzle-orm';
9
+ import {db} from '#db/db.js';
10
+ import {createInvitation} from '#db/invitations.js';
11
+ import {listMembershipsByUser} from '#db/memberships.js';
12
+ import {workspacesOutbox} from '#db/schema/outbox.js';
13
+ import {workspacesRoutes} from '#presentation/routes/index.js';
14
+
15
+ const testConfig = vi.hoisted(
16
+ (): {
17
+ captured: MailMessage[];
18
+ mailer: Mailer;
19
+ secret: string;
20
+ clientBaseUrl: string;
21
+ } => {
22
+ const captured: MailMessage[] = [];
23
+ const mailer: Mailer = {
24
+ send: (message) => {
25
+ captured.push(message);
26
+ return Promise.resolve();
27
+ },
28
+ };
29
+ return {
30
+ captured,
31
+ mailer,
32
+ secret: 'route-tests-secret',
33
+ clientBaseUrl: 'https://app.example.test',
34
+ };
35
+ },
36
+ );
37
+
38
+ vi.mock('#config.js', () => ({
39
+ config: {
40
+ CLIENT_BASE_URL: testConfig.clientBaseUrl,
41
+ },
42
+ mailer: testConfig.mailer,
43
+ }));
44
+
45
+ const TOKEN_RE = /token=([\w\-_=]+)/;
46
+ const BEARER_RE = /^Bearer /u;
47
+
48
+ export const ROUTE_TEST_SECRET = testConfig.secret;
49
+
50
+ const fakeUserAuth: AuthMethod = {
51
+ name: AUTH_USER,
52
+ authenticate: async (request) => {
53
+ const raw = request.headers.authorization?.replace(BEARER_RE, '');
54
+ if (raw?.startsWith('claim:')) {
55
+ const [, userId, email, workspaceId] = raw.split(':');
56
+ if (!userId || !email || !workspaceId) throw new Error('Invalid test user claim token');
57
+ setUserContext(
58
+ request,
59
+ buildUserContext({
60
+ userId,
61
+ email,
62
+ memberships: [{workspaceId, role: 'admin'}],
63
+ }),
64
+ );
65
+ return;
66
+ }
67
+
68
+ const [userId, email, encodedName] = raw?.startsWith('user:') ? raw.slice(5).split(':') : [];
69
+ if (!userId || !email) throw new Error('Invalid test user token');
70
+ const memberships = await listMembershipsByUser({userId});
71
+ setUserContext(
72
+ request,
73
+ buildUserContext({
74
+ userId,
75
+ email,
76
+ name: encodedName ? decodeURIComponent(encodedName) : null,
77
+ memberships: memberships.map((m) => ({workspaceId: m.workspaceId, role: 'admin' as const})),
78
+ }),
79
+ );
80
+ },
81
+ };
82
+
83
+ export function resetCapturedMail(): void {
84
+ testConfig.captured.length = 0;
85
+ }
86
+
87
+ export function capturedMail(): MailMessage[] {
88
+ return testConfig.captured;
89
+ }
90
+
91
+ export async function invitationOutboxEventsTo(email: string) {
92
+ return await db()
93
+ .select()
94
+ .from(workspacesOutbox)
95
+ .where(
96
+ and(
97
+ eq(workspacesOutbox.eventType, WORKSPACES_INVITATION_SEND_REQUESTED),
98
+ sql`${workspacesOutbox.payload}->>'email' = ${email}`,
99
+ ),
100
+ )
101
+ .orderBy(desc(workspacesOutbox.createdAt));
102
+ }
103
+
104
+ export function uniqueEmail(prefix: string): string {
105
+ return `${prefix}-${crypto.randomUUID()}@example.com`;
106
+ }
107
+
108
+ export function extractToken(url: string): string {
109
+ const match = url.match(TOKEN_RE);
110
+ expect(match?.[1]).toBeDefined();
111
+ return match?.[1] ?? '';
112
+ }
113
+
114
+ export function getSetCookie(res: {headers: OutgoingHttpHeaders}): string {
115
+ const header = res.headers['set-cookie'];
116
+ const value = Array.isArray(header) ? header[0] : typeof header === 'string' ? header : undefined;
117
+ expect(value).toBeDefined();
118
+ return value ?? '';
119
+ }
120
+
121
+ export function cookieHeader(setCookie: string): string {
122
+ const [cookie] = setCookie.split(';');
123
+ expect(cookie).toBeDefined();
124
+ return cookie ?? '';
125
+ }
126
+
127
+ export function latestMailTo(email: string): MailMessage {
128
+ const message = [...testConfig.captured].reverse().find((mail) => mail.to === email);
129
+ expect(message).toBeDefined();
130
+ return message as MailMessage;
131
+ }
132
+
133
+ export async function latestInvitationLinkTo(email: string): Promise<string> {
134
+ const event = (await invitationOutboxEventsTo(email))[0];
135
+ expect(event).toBeDefined();
136
+ const payload = event?.payload as {inviteLink?: string} | undefined;
137
+ expect(payload?.inviteLink).toBeDefined();
138
+ return payload?.inviteLink ?? '';
139
+ }
140
+
141
+ export async function createWorkspacesTestApp(): Promise<FastifyInstance> {
142
+ return await createApp({
143
+ auth: [fakeUserAuth],
144
+ routes: workspacesRoutes,
145
+ swagger: false,
146
+ });
147
+ }
148
+
149
+ export async function signup(
150
+ app: FastifyInstance,
151
+ params: {email: string; password: string; name?: string},
152
+ ) {
153
+ await Promise.resolve();
154
+ void app;
155
+ return {statusCode: 201, json: () => ({user: {id: crypto.randomUUID(), email: params.email}})};
156
+ }
157
+
158
+ export async function verifyEmail(app: FastifyInstance, email: string): Promise<void> {
159
+ await Promise.resolve();
160
+ void app;
161
+ void email;
162
+ }
163
+
164
+ export async function login(app: FastifyInstance, params: {email: string; password: string}) {
165
+ await Promise.resolve();
166
+ void app;
167
+ return {
168
+ statusCode: 200,
169
+ headers: {'set-cookie': 'shipfox_refresh_token=test; Path=/auth'},
170
+ json: () => {
171
+ const userId = crypto.randomUUID();
172
+ return {token: `user:${userId}:${params.email}`, user: {id: userId}};
173
+ },
174
+ };
175
+ }
176
+
177
+ export async function signupVerifyLogin(
178
+ app: FastifyInstance,
179
+ prefix: string,
180
+ options: {name?: string} = {},
181
+ ): Promise<{
182
+ email: string;
183
+ name: string | null;
184
+ password: string;
185
+ refreshCookie: string;
186
+ token: string;
187
+ userId: string;
188
+ }> {
189
+ await Promise.resolve();
190
+ const email = uniqueEmail(prefix);
191
+ const password = 'correct horse battery staple';
192
+ void app;
193
+ const userId = crypto.randomUUID();
194
+ const name = options.name ?? null;
195
+ const tokenName = name ? `:${encodeURIComponent(name)}` : '';
196
+ return {
197
+ email,
198
+ name,
199
+ password,
200
+ refreshCookie: 'shipfox_refresh_token=test; Path=/auth',
201
+ token: `user:${userId}:${email}${tokenName}`,
202
+ userId,
203
+ };
204
+ }
205
+
206
+ export async function createWorkspace(
207
+ app: FastifyInstance,
208
+ token: string,
209
+ name = `Workspace ${crypto.randomUUID()}`,
210
+ ): Promise<string> {
211
+ const res = await app.inject({
212
+ method: 'POST',
213
+ url: '/workspaces',
214
+ headers: {authorization: `Bearer ${token}`},
215
+ payload: {name},
216
+ });
217
+
218
+ expect(res.statusCode).toBe(201);
219
+ return res.json().id;
220
+ }
221
+
222
+ export async function createInvite(
223
+ app: FastifyInstance,
224
+ params: {token: string; workspaceId: string; email: string},
225
+ ): Promise<{id: string; rawToken: string}> {
226
+ const res = await app.inject({
227
+ method: 'POST',
228
+ url: `/workspaces/${params.workspaceId}/invitations`,
229
+ headers: {authorization: `Bearer ${params.token}`},
230
+ payload: {email: params.email},
231
+ });
232
+
233
+ expect(res.statusCode).toBe(201);
234
+ return {
235
+ id: res.json().id,
236
+ rawToken: extractToken(await latestInvitationLinkTo(params.email)),
237
+ };
238
+ }
239
+
240
+ export async function createExpiredInvite(params: {
241
+ workspaceId: string;
242
+ email: string;
243
+ invitedByUserId: string;
244
+ }): Promise<string> {
245
+ const rawToken = `expired-${crypto.randomUUID()}`;
246
+ await createInvitation({
247
+ workspaceId: params.workspaceId,
248
+ email: params.email,
249
+ hashedToken: hashOpaqueToken(rawToken),
250
+ expiresAt: new Date(Date.now() - 60_000),
251
+ invitedByUserId: params.invitedByUserId,
252
+ skipEmail: true,
253
+ });
254
+ return rawToken;
255
+ }
package/test/setup.ts ADDED
@@ -0,0 +1,17 @@
1
+ import './env.js';
2
+ import {closePostgresClient, createPostgresClient} from '@shipfox/node-postgres';
3
+ import {afterAll, afterEach, beforeAll, vi} from '@shipfox/vitest/vi';
4
+ import {closeDb} from '#db/index.js';
5
+
6
+ beforeAll(() => {
7
+ createPostgresClient();
8
+ });
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ });
13
+
14
+ afterAll(async () => {
15
+ closeDb();
16
+ await closePostgresClient();
17
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@shipfox/ts-config/node",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["**/*.test.tsx", "**/*.test.ts"]
9
+ }