@selvajs/server 1.2.0 → 1.3.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 (217) hide show
  1. package/dist/access/guards.d.ts +213 -0
  2. package/dist/access/guards.d.ts.map +1 -0
  3. package/dist/access/guards.js +417 -0
  4. package/dist/access/guards.js.map +1 -0
  5. package/dist/access/index.d.ts +2 -0
  6. package/dist/access/index.d.ts.map +1 -1
  7. package/dist/access/index.js +6 -0
  8. package/dist/access/index.js.map +1 -1
  9. package/dist/access/permissions-scope.d.ts +15 -0
  10. package/dist/access/permissions-scope.d.ts.map +1 -0
  11. package/dist/access/permissions-scope.js +23 -0
  12. package/dist/access/permissions-scope.js.map +1 -0
  13. package/dist/api/bodies.d.ts +104 -0
  14. package/dist/api/bodies.d.ts.map +1 -0
  15. package/dist/api/bodies.js +87 -0
  16. package/dist/api/bodies.js.map +1 -0
  17. package/dist/api/deps.d.ts +135 -0
  18. package/dist/api/deps.d.ts.map +1 -0
  19. package/dist/api/deps.js +55 -0
  20. package/dist/api/deps.js.map +1 -0
  21. package/dist/api/errors.d.ts +35 -0
  22. package/dist/api/errors.d.ts.map +1 -0
  23. package/dist/api/errors.js +63 -0
  24. package/dist/api/errors.js.map +1 -0
  25. package/dist/api/index.d.ts +12 -0
  26. package/dist/api/index.d.ts.map +1 -0
  27. package/dist/api/index.js +15 -0
  28. package/dist/api/index.js.map +1 -0
  29. package/dist/api/map-core-error.d.ts +19 -0
  30. package/dist/api/map-core-error.d.ts.map +1 -0
  31. package/dist/api/map-core-error.js +49 -0
  32. package/dist/api/map-core-error.js.map +1 -0
  33. package/dist/api/pagination.d.ts +19 -0
  34. package/dist/api/pagination.d.ts.map +1 -0
  35. package/dist/api/pagination.js +40 -0
  36. package/dist/api/pagination.js.map +1 -0
  37. package/dist/api/request.d.ts +93 -0
  38. package/dist/api/request.d.ts.map +1 -0
  39. package/dist/api/request.js +150 -0
  40. package/dist/api/request.js.map +1 -0
  41. package/dist/api/respond.d.ts +29 -0
  42. package/dist/api/respond.d.ts.map +1 -0
  43. package/dist/api/respond.js +59 -0
  44. package/dist/api/respond.js.map +1 -0
  45. package/dist/api/responses-schema.d.ts +158 -0
  46. package/dist/api/responses-schema.d.ts.map +1 -0
  47. package/dist/api/responses-schema.js +104 -0
  48. package/dist/api/responses-schema.js.map +1 -0
  49. package/dist/api/responses.d.ts +19 -0
  50. package/dist/api/responses.d.ts.map +1 -0
  51. package/dist/api/responses.js +21 -0
  52. package/dist/api/responses.js.map +1 -0
  53. package/dist/api/shaped.d.ts +23 -0
  54. package/dist/api/shaped.d.ts.map +1 -0
  55. package/dist/api/shaped.js +29 -0
  56. package/dist/api/shaped.js.map +1 -0
  57. package/dist/api/types.d.ts +41 -0
  58. package/dist/api/types.d.ts.map +1 -0
  59. package/dist/api/types.js +12 -0
  60. package/dist/api/types.js.map +1 -0
  61. package/dist/compute/errors.d.ts +18 -0
  62. package/dist/compute/errors.d.ts.map +1 -0
  63. package/dist/compute/errors.js +21 -0
  64. package/dist/compute/errors.js.map +1 -0
  65. package/dist/compute/evict-changed-servers.d.ts +22 -0
  66. package/dist/compute/evict-changed-servers.d.ts.map +1 -0
  67. package/dist/compute/evict-changed-servers.js +31 -0
  68. package/dist/compute/evict-changed-servers.js.map +1 -0
  69. package/dist/compute/idempotency-http.d.ts +47 -0
  70. package/dist/compute/idempotency-http.d.ts.map +1 -0
  71. package/dist/compute/idempotency-http.js +51 -0
  72. package/dist/compute/idempotency-http.js.map +1 -0
  73. package/dist/compute/index.d.ts +5 -0
  74. package/dist/compute/index.d.ts.map +1 -1
  75. package/dist/compute/index.js +5 -0
  76. package/dist/compute/index.js.map +1 -1
  77. package/dist/compute/resolve-server.d.ts +25 -0
  78. package/dist/compute/resolve-server.d.ts.map +1 -0
  79. package/dist/compute/resolve-server.js +35 -0
  80. package/dist/compute/resolve-server.js.map +1 -0
  81. package/dist/compute/server-config-write.d.ts +52 -0
  82. package/dist/compute/server-config-write.d.ts.map +1 -0
  83. package/dist/compute/server-config-write.js +100 -0
  84. package/dist/compute/server-config-write.js.map +1 -0
  85. package/dist/definitions/index.d.ts +1 -0
  86. package/dist/definitions/index.d.ts.map +1 -1
  87. package/dist/definitions/index.js +1 -0
  88. package/dist/definitions/index.js.map +1 -1
  89. package/dist/definitions/visibility.d.ts +74 -0
  90. package/dist/definitions/visibility.d.ts.map +1 -0
  91. package/dist/definitions/visibility.js +117 -0
  92. package/dist/definitions/visibility.js.map +1 -0
  93. package/dist/handlers/definitionVersions.d.ts +30 -0
  94. package/dist/handlers/definitionVersions.d.ts.map +1 -0
  95. package/dist/handlers/definitionVersions.js +93 -0
  96. package/dist/handlers/definitionVersions.js.map +1 -0
  97. package/dist/handlers/definitions.d.ts +35 -0
  98. package/dist/handlers/definitions.d.ts.map +1 -0
  99. package/dist/handlers/definitions.js +207 -0
  100. package/dist/handlers/definitions.js.map +1 -0
  101. package/dist/handlers/index.d.ts +27 -0
  102. package/dist/handlers/index.d.ts.map +1 -0
  103. package/dist/handlers/index.js +29 -0
  104. package/dist/handlers/index.js.map +1 -0
  105. package/dist/handlers/invite-lookup.d.ts +11 -0
  106. package/dist/handlers/invite-lookup.d.ts.map +1 -0
  107. package/dist/handlers/invite-lookup.js +20 -0
  108. package/dist/handlers/invite-lookup.js.map +1 -0
  109. package/dist/handlers/invites.d.ts +31 -0
  110. package/dist/handlers/invites.d.ts.map +1 -0
  111. package/dist/handlers/invites.js +148 -0
  112. package/dist/handlers/invites.js.map +1 -0
  113. package/dist/handlers/me.d.ts +8 -0
  114. package/dist/handlers/me.d.ts.map +1 -0
  115. package/dist/handlers/me.js +21 -0
  116. package/dist/handlers/me.js.map +1 -0
  117. package/dist/handlers/me.starred.d.ts +11 -0
  118. package/dist/handlers/me.starred.d.ts.map +1 -0
  119. package/dist/handlers/me.starred.js +21 -0
  120. package/dist/handlers/me.starred.js.map +1 -0
  121. package/dist/handlers/orgAssets.d.ts +13 -0
  122. package/dist/handlers/orgAssets.d.ts.map +1 -0
  123. package/dist/handlers/orgAssets.js +54 -0
  124. package/dist/handlers/orgAssets.js.map +1 -0
  125. package/dist/handlers/orgCompute.d.ts +16 -0
  126. package/dist/handlers/orgCompute.d.ts.map +1 -0
  127. package/dist/handlers/orgCompute.js +96 -0
  128. package/dist/handlers/orgCompute.js.map +1 -0
  129. package/dist/handlers/orgMembers.d.ts +29 -0
  130. package/dist/handlers/orgMembers.d.ts.map +1 -0
  131. package/dist/handlers/orgMembers.js +200 -0
  132. package/dist/handlers/orgMembers.js.map +1 -0
  133. package/dist/handlers/orgs.d.ts +16 -0
  134. package/dist/handlers/orgs.d.ts.map +1 -0
  135. package/dist/handlers/orgs.js +26 -0
  136. package/dist/handlers/orgs.js.map +1 -0
  137. package/dist/handlers/projectMembers.d.ts +15 -0
  138. package/dist/handlers/projectMembers.d.ts.map +1 -0
  139. package/dist/handlers/projectMembers.js +95 -0
  140. package/dist/handlers/projectMembers.js.map +1 -0
  141. package/dist/handlers/projects.d.ts +21 -0
  142. package/dist/handlers/projects.d.ts.map +1 -0
  143. package/dist/handlers/projects.js +164 -0
  144. package/dist/handlers/projects.js.map +1 -0
  145. package/dist/handlers/reclaim.d.ts +10 -0
  146. package/dist/handlers/reclaim.d.ts.map +1 -0
  147. package/dist/handlers/reclaim.js +38 -0
  148. package/dist/handlers/reclaim.js.map +1 -0
  149. package/dist/handlers/services.d.ts +22 -0
  150. package/dist/handlers/services.d.ts.map +1 -0
  151. package/dist/handlers/services.js +37 -0
  152. package/dist/handlers/services.js.map +1 -0
  153. package/dist/handlers/shareLinks.d.ts +23 -0
  154. package/dist/handlers/shareLinks.d.ts.map +1 -0
  155. package/dist/handlers/shareLinks.js +85 -0
  156. package/dist/handlers/shareLinks.js.map +1 -0
  157. package/dist/http/index.d.ts +1 -0
  158. package/dist/http/index.d.ts.map +1 -1
  159. package/dist/http/index.js +1 -0
  160. package/dist/http/index.js.map +1 -1
  161. package/dist/http/login-rate-limit.d.ts +72 -0
  162. package/dist/http/login-rate-limit.d.ts.map +1 -0
  163. package/dist/http/login-rate-limit.js +84 -0
  164. package/dist/http/login-rate-limit.js.map +1 -0
  165. package/dist/notifications/deliver-invite.d.ts +27 -0
  166. package/dist/notifications/deliver-invite.d.ts.map +1 -0
  167. package/dist/notifications/deliver-invite.js +32 -0
  168. package/dist/notifications/deliver-invite.js.map +1 -0
  169. package/dist/notifications/index.d.ts +1 -0
  170. package/dist/notifications/index.d.ts.map +1 -1
  171. package/dist/notifications/index.js +1 -0
  172. package/dist/notifications/index.js.map +1 -1
  173. package/dist/organizations/index.d.ts +3 -0
  174. package/dist/organizations/index.d.ts.map +1 -0
  175. package/dist/organizations/index.js +5 -0
  176. package/dist/organizations/index.js.map +1 -0
  177. package/dist/organizations/list-all-members.d.ts +10 -0
  178. package/dist/organizations/list-all-members.d.ts.map +1 -0
  179. package/dist/organizations/list-all-members.js +36 -0
  180. package/dist/organizations/list-all-members.js.map +1 -0
  181. package/dist/organizations/org-asset-service.d.ts +39 -0
  182. package/dist/organizations/org-asset-service.d.ts.map +1 -0
  183. package/dist/organizations/org-asset-service.js +71 -0
  184. package/dist/organizations/org-asset-service.js.map +1 -0
  185. package/dist/projects/create-project.d.ts +30 -0
  186. package/dist/projects/create-project.d.ts.map +1 -0
  187. package/dist/projects/create-project.js +56 -0
  188. package/dist/projects/create-project.js.map +1 -0
  189. package/dist/projects/index.d.ts +2 -0
  190. package/dist/projects/index.d.ts.map +1 -0
  191. package/dist/projects/index.js +3 -0
  192. package/dist/projects/index.js.map +1 -0
  193. package/dist/providers/auth-lookup.d.ts +14 -0
  194. package/dist/providers/auth-lookup.d.ts.map +1 -0
  195. package/dist/providers/auth-lookup.js +34 -0
  196. package/dist/providers/auth-lookup.js.map +1 -0
  197. package/dist/providers/index.d.ts +1 -0
  198. package/dist/providers/index.d.ts.map +1 -1
  199. package/dist/providers/index.js +1 -0
  200. package/dist/providers/index.js.map +1 -1
  201. package/dist/testing/harness.d.ts +168 -0
  202. package/dist/testing/harness.d.ts.map +1 -0
  203. package/dist/testing/harness.js +314 -0
  204. package/dist/testing/harness.js.map +1 -0
  205. package/dist/testing/index.d.ts +18 -0
  206. package/dist/testing/index.d.ts.map +1 -0
  207. package/dist/testing/index.js +15 -0
  208. package/dist/testing/index.js.map +1 -0
  209. package/dist/testing/scenarios.d.ts +67 -0
  210. package/dist/testing/scenarios.d.ts.map +1 -0
  211. package/dist/testing/scenarios.js +104 -0
  212. package/dist/testing/scenarios.js.map +1 -0
  213. package/dist/testing/seed-adapter.d.ts +31 -0
  214. package/dist/testing/seed-adapter.d.ts.map +1 -0
  215. package/dist/testing/seed-adapter.js +19 -0
  216. package/dist/testing/seed-adapter.js.map +1 -0
  217. package/package.json +30 -2
@@ -0,0 +1,314 @@
1
+ /**
2
+ * A host-agnostic harness for testing API handlers.
3
+ *
4
+ * Handlers are transport-free, but the tests around them were not: they built
5
+ * a SvelteKit `RequestEvent` and imported the Selva app's fixtures, so a second
6
+ * host could run the handlers and inherit none of the tests proving they work.
7
+ * That is the duplication this removes — a host wires a `TestHarness` once and
8
+ * gets the shared suites.
9
+ *
10
+ * Two things stay injected because no interface supplies them:
11
+ * - {@link SeedAuthAdapter}, since identity has no portable creation method.
12
+ * - `mapError`, since folding a host's own domain errors into the envelope is
13
+ * by definition host-specific. Omitting it is not neutral: a host whose
14
+ * guards throw something `runHandler` does not recognize gets a 500 where
15
+ * production returns 403, and every status assertion silently weakens.
16
+ */
17
+ import { randomUUID } from 'node:crypto';
18
+ import { SYSTEM_CONTEXT, DEFAULT_ORG_PERMISSIONS, DEFAULT_SHARE_LINK_MAX_SOLVES, emptyProfile } from '@selvajs/platform';
19
+ import { depsFromConfig, runHandler } from '../api/index.js';
20
+ /**
21
+ * Swallows output unless `LOUD=1`.
22
+ *
23
+ * `runHandler` logs through `req.log` on the 500 path. Without a logger a test
24
+ * that trips an unexpected 500 dies inside the error handler itself, which
25
+ * hides the error that caused it.
26
+ */
27
+ export const silentLog = {
28
+ error: (...args) => {
29
+ if (process.env.LOUD)
30
+ console.error('[test log]', ...args);
31
+ },
32
+ warn: () => { },
33
+ info: () => { },
34
+ debug: () => { },
35
+ child: () => silentLog
36
+ };
37
+ export async function seedUser(h, email, platformPermissions = []) {
38
+ // Identity row then data-layer row, in that order — the sequence production
39
+ // runs through its auth hook. Reversing it leaves a data row keyed to an id
40
+ // no identity claims.
41
+ const u = await h.auth.createUser(email);
42
+ await h.config.data.ensureUser(SYSTEM_CONTEXT, u.id);
43
+ if (platformPermissions.length > 0) {
44
+ await h.config.data.permissions.set(SYSTEM_CONTEXT, u.id, platformPermissions);
45
+ }
46
+ // `AuthUser.email` is optional — a header-auth provider need not report one.
47
+ // The seeded email is the caller's own input, so hand that back rather than
48
+ // widening every test's `email` to `string | undefined`.
49
+ return { id: u.id, email };
50
+ }
51
+ export async function seedOrg(h, opts) {
52
+ const now = new Date().toISOString();
53
+ const org = {
54
+ id: randomUUID(),
55
+ name: opts.name,
56
+ slug: opts.slug,
57
+ ownerId: opts.ownerId,
58
+ createdBy: opts.ownerId,
59
+ updatedBy: opts.ownerId,
60
+ createdAt: now,
61
+ updatedAt: now,
62
+ deletedAt: null
63
+ };
64
+ await h.config.data.orgs.createOrg(SYSTEM_CONTEXT, org);
65
+ return org;
66
+ }
67
+ export async function seedOrgMember(h, opts) {
68
+ const now = new Date().toISOString();
69
+ const member = {
70
+ orgId: opts.orgId,
71
+ userId: opts.userId,
72
+ role: opts.role,
73
+ permissions: opts.permissions ?? [...DEFAULT_ORG_PERMISSIONS[opts.role]],
74
+ joinedAt: now,
75
+ updatedAt: now,
76
+ updatedBy: opts.userId,
77
+ deletedAt: null
78
+ };
79
+ await h.config.data.orgs.addOrgMember(SYSTEM_CONTEXT, member);
80
+ return member;
81
+ }
82
+ export async function seedProject(h, opts) {
83
+ const now = new Date().toISOString();
84
+ const project = {
85
+ id: randomUUID(),
86
+ orgId: opts.orgId,
87
+ name: opts.name,
88
+ slug: opts.slug,
89
+ visibility: opts.visibility ?? 'private',
90
+ ownerId: opts.ownerId,
91
+ createdBy: opts.ownerId,
92
+ updatedBy: opts.ownerId,
93
+ autoJoinOnUpload: opts.autoJoinOnUpload ?? false,
94
+ createdAt: now,
95
+ updatedAt: now,
96
+ deletedAt: null
97
+ };
98
+ await h.config.data.projects.createProject(SYSTEM_CONTEXT, project);
99
+ await h.config.data.projects.addProjectMember(SYSTEM_CONTEXT, {
100
+ projectId: project.id,
101
+ userId: opts.ownerId,
102
+ role: 'owner',
103
+ joinedAt: now,
104
+ updatedAt: now,
105
+ updatedBy: opts.ownerId,
106
+ deletedAt: null
107
+ });
108
+ return project;
109
+ }
110
+ export async function seedProjectMember(h, opts) {
111
+ const now = new Date().toISOString();
112
+ const member = {
113
+ projectId: opts.projectId,
114
+ userId: opts.userId,
115
+ role: opts.role,
116
+ joinedAt: now,
117
+ updatedAt: now,
118
+ updatedBy: opts.userId,
119
+ deletedAt: null
120
+ };
121
+ await h.config.data.projects.addProjectMember(SYSTEM_CONTEXT, member);
122
+ return member;
123
+ }
124
+ /**
125
+ * Grant platform-wide permissions to a seeded user.
126
+ *
127
+ * Replaces the whole set rather than adding to it, matching
128
+ * `IPermissionStore.set` — a test that means to add has to pass the existing
129
+ * permissions back in.
130
+ */
131
+ export async function grantPlatformPermissions(h, userId, perms) {
132
+ await h.config.data.permissions.set(SYSTEM_CONTEXT, userId, perms);
133
+ }
134
+ /**
135
+ * A definition with one version, written through the host's `DefinitionService`
136
+ * rather than straight into the store.
137
+ *
138
+ * Going through the service is what makes the fixture match production: it is
139
+ * what allocates the version, writes the blob, and moves the channel pointer,
140
+ * and a test that seeded the record alone would exercise a definition no solve
141
+ * path can load.
142
+ */
143
+ export async function seedDefinition(h, opts) {
144
+ const service = h.deps?.services?.definitions;
145
+ if (!service)
146
+ throw new Error('TestHarness.deps.services.definitions is required to seed one');
147
+ const input = {
148
+ guid: opts.guid ?? randomUUID(),
149
+ projectId: opts.projectId,
150
+ ownerId: opts.ownerId,
151
+ fileExt: 'gh',
152
+ displayName: opts.displayName ?? 'Test Definition',
153
+ originalFilename: 'test.gh'
154
+ };
155
+ const file = new TextEncoder().encode('FAKE_GH_BYTES');
156
+ // Admin-shaped ctx so the service's own tenancy checks don't refuse a seed.
157
+ const ctx = {
158
+ userId: opts.ownerId,
159
+ actingOrgId: undefined,
160
+ platformPermissions: ['instance_admin'],
161
+ orgPermissions: []
162
+ };
163
+ const schema = { name: 'Test', inputs: [], outputs: [] };
164
+ return service.create(ctx, input, file, schema);
165
+ }
166
+ /**
167
+ * A share link plus the plaintext token behind it.
168
+ *
169
+ * Minted through the harness's own codec, so the stored hash verifies against
170
+ * the same secret a handler resolves from `deps.tokens.shareLinks` — a second
171
+ * codec would hash to something no handler could ever match.
172
+ */
173
+ export async function seedShareLink(h, opts) {
174
+ const codec = h.deps?.tokens?.shareLinks;
175
+ if (!codec)
176
+ throw new Error('TestHarness.deps.tokens.shareLinks is required to seed a link');
177
+ const rawToken = codec.mintRawToken();
178
+ const link = {
179
+ id: randomUUID(),
180
+ definitionId: opts.definitionId,
181
+ channel: opts.channel ?? 'live',
182
+ tokenHash: codec.hashToken(rawToken),
183
+ createdBy: opts.createdBy,
184
+ createdAt: new Date().toISOString(),
185
+ expiresAt: opts.expiresAt ?? null,
186
+ revokedAt: null,
187
+ allowSolve: opts.allowSolve ?? true,
188
+ maxSolves: opts.maxSolves === undefined ? DEFAULT_SHARE_LINK_MAX_SOLVES : opts.maxSolves,
189
+ solveCount: 0
190
+ };
191
+ await h.config.data.shareLinks.create({
192
+ userId: opts.createdBy,
193
+ actingOrgId: undefined,
194
+ platformPermissions: ['instance_admin'],
195
+ orgPermissions: []
196
+ }, link);
197
+ return { link, rawToken };
198
+ }
199
+ // ============================================================================
200
+ // Acting identity
201
+ // ============================================================================
202
+ /**
203
+ * The harness a `callHandler` call should build its deps from, carried on the
204
+ * locals rather than passed alongside them.
205
+ *
206
+ * A test already threads `locals` from `actAs` into every call; making the
207
+ * harness a second argument would mean passing the same two values everywhere
208
+ * and would let them disagree — locals from one stack, deps from another, which
209
+ * type-checks and silently tests nothing.
210
+ */
211
+ const HARNESS = Symbol.for('@selvajs/server/testing.harness');
212
+ /**
213
+ * Build the per-request identity a handler sees.
214
+ *
215
+ * Mirrors what a host's auth hook produces: one membership lookup, plus the
216
+ * instance-admin fallback to the first listed org for an admin who belongs to
217
+ * none. Keeping this aligned with the host is the point of testing against real
218
+ * stores rather than mocks — a fixture that invented its own `ctx` would prove
219
+ * the handler works under scope no request ever has.
220
+ */
221
+ export async function actAs(h, userId) {
222
+ const user = await h.auth.findById(userId);
223
+ if (!user)
224
+ throw new Error(`actAs: user not found: ${userId}`);
225
+ const platformPermissions = await h.config.data.permissions.getFor(SYSTEM_CONTEXT, user.id);
226
+ const membership = await h.config.data.orgs.findUserMembership(SYSTEM_CONTEXT, user.id);
227
+ let actingOrgId = membership?.org.id;
228
+ const orgPermissions = membership ? [...membership.member.permissions] : [];
229
+ if (!actingOrgId && platformPermissions.includes('instance_admin')) {
230
+ const firstOrgPage = await h.config.data.orgs.listOrgs(SYSTEM_CONTEXT, { limit: 1 });
231
+ actingOrgId = firstOrgPage.items[0]?.id;
232
+ }
233
+ const profile = (await h.config.data.userProfile.getProfile(SYSTEM_CONTEXT, user.id)) ?? emptyProfile(user.id);
234
+ return {
235
+ user,
236
+ ctx: { userId: user.id, actingOrgId, platformPermissions, orgPermissions },
237
+ profile,
238
+ providers: h.config,
239
+ log: h.log ?? silentLog,
240
+ [HARNESS]: h
241
+ };
242
+ }
243
+ export function anon(h) {
244
+ return { providers: h.config, [HARNESS]: h };
245
+ }
246
+ /**
247
+ * Invoke an `ApiHandler` with no host framework in the loop.
248
+ *
249
+ * Still goes through `runHandler`, because that is the contract: it is what
250
+ * turns a thrown `ApiError` — or a host error `mapError` recognizes — into a
251
+ * status. Calling the handler bare would make every failure an exception and
252
+ * every status assertion untestable.
253
+ */
254
+ export async function callHandler(handler, opts) {
255
+ const url = new URL(opts.url ?? 'http://test.local/');
256
+ const init = {
257
+ method: opts.body !== undefined ? 'POST' : 'GET',
258
+ headers: opts.headers ?? {}
259
+ };
260
+ if (opts.body !== undefined) {
261
+ // FormData carries its own multipart content-type with a generated
262
+ // boundary; forcing JSON onto it makes `request.formData()` throw.
263
+ if (opts.body instanceof FormData) {
264
+ init.body = opts.body;
265
+ }
266
+ else {
267
+ init.headers['content-type'] ??= 'application/json';
268
+ init.body = typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body);
269
+ }
270
+ }
271
+ const locals = opts.locals;
272
+ const h = locals[HARNESS];
273
+ if (!h) {
274
+ // Without the harness there is no `mapError`, so every guard rejection
275
+ // would fall through to a 500 and each expected 403 would read as a
276
+ // passing-but-meaningless assertion. Fail loudly instead.
277
+ throw new Error('callHandler: locals carry no harness — build them with `actAs()` or `anon()`.');
278
+ }
279
+ const req = {
280
+ ctx: locals.ctx,
281
+ user: locals.user,
282
+ profile: locals.profile,
283
+ log: locals.log ?? h.log ?? silentLog,
284
+ params: opts.params ?? {},
285
+ url,
286
+ request: new Request(url.toString(), init),
287
+ deps: depsFromConfig(locals.providers, h.deps?.services ?? {}, {
288
+ tokens: h.deps?.tokens,
289
+ uploadLimits: h.deps?.uploadLimits
290
+ })
291
+ };
292
+ return readResponse(await runHandler(handler, req, { fallback: 'Test', mapError: h.mapError }));
293
+ }
294
+ async function readResponse(res) {
295
+ const contentType = res.headers.get('content-type') ?? '';
296
+ const out = { status: res.status, headers: res.headers };
297
+ if (res.headers.has('location'))
298
+ out.location = res.headers.get('location');
299
+ if (contentType.includes('application/json')) {
300
+ const text = await res.text();
301
+ out.text = text;
302
+ try {
303
+ out.json = text ? JSON.parse(text) : undefined;
304
+ }
305
+ catch {
306
+ // Leave it as text — a malformed body is the assertion's problem.
307
+ }
308
+ }
309
+ else {
310
+ out.text = await res.text();
311
+ }
312
+ return out;
313
+ }
314
+ //# sourceMappingURL=harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/testing/harness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Test harness for the transport-free API handlers.
3
+ *
4
+ * A host builds a {@link TestHarness} around its own provider stack and gets
5
+ * the seeders, `actAs`, and `callHandler` that the handler tests are written
6
+ * against — so the tests travel with the handlers instead of being rewritten
7
+ * per host.
8
+ *
9
+ * This entry point constructs no provider. Which stack a test runs on is the
10
+ * host's decision, and a package that picked one would drag that provider into
11
+ * every consumer's dependency tree.
12
+ */
13
+ export type { SeedAuthAdapter } from './seed-adapter.js';
14
+ export { seedUser, seedOrg, seedOrgMember, seedProject, seedProjectMember, seedDefinition, seedShareLink, grantPlatformPermissions, actAs, anon, callHandler, silentLog } from './harness.js';
15
+ export type { TestHarness, SeededUser, SeededDefinition, SeededShareLink, ActingLocals, CallHandlerOpts, CallResult } from './harness.js';
16
+ export { seedAcme, seedBigClient, seedThirdOrg, seedCommons } from './scenarios.js';
17
+ export type { AcmeFixture, BigClientFixture, ThirdOrgFixture, CommonsFixture } from './scenarios.js';
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACN,QAAQ,EACR,OAAO,EACP,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,wBAAwB,EACxB,KAAK,EACL,IAAI,EACJ,WAAW,EACX,SAAS,EACT,MAAM,cAAc,CAAC;AACtB,YAAY,EACX,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,eAAe,EACf,UAAU,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACpF,YAAY,EACX,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Test harness for the transport-free API handlers.
3
+ *
4
+ * A host builds a {@link TestHarness} around its own provider stack and gets
5
+ * the seeders, `actAs`, and `callHandler` that the handler tests are written
6
+ * against — so the tests travel with the handlers instead of being rewritten
7
+ * per host.
8
+ *
9
+ * This entry point constructs no provider. Which stack a test runs on is the
10
+ * host's decision, and a package that picked one would drag that provider into
11
+ * every consumer's dependency tree.
12
+ */
13
+ export { seedUser, seedOrg, seedOrgMember, seedProject, seedProjectMember, seedDefinition, seedShareLink, grantPlatformPermissions, actAs, anon, callHandler, silentLog } from './harness.js';
14
+ export { seedAcme, seedBigClient, seedThirdOrg, seedCommons } from './scenarios.js';
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EACN,QAAQ,EACR,OAAO,EACP,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,wBAAwB,EACxB,KAAK,EACL,IAAI,EACJ,WAAW,EACX,SAAS,EACT,MAAM,cAAc,CAAC;AAUtB,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The shared cast the handler tests are written against.
3
+ *
4
+ * These compose only the seeders in `./harness.ts`, so they run on whatever
5
+ * provider stack the host built its {@link TestHarness} around. They live here
6
+ * rather than in a host's fixtures because a handler test that travels with its
7
+ * handler needs the same two tenants wherever it runs — a host that reseeds its
8
+ * own Acme with different roles gets different results from the same test.
9
+ */
10
+ import type { Organization, Project } from '@selvajs/platform';
11
+ import type { SeededDefinition, SeededUser, TestHarness } from './harness.js';
12
+ export interface AcmeFixture {
13
+ acme: Organization;
14
+ alice: SeededUser;
15
+ bob: SeededUser;
16
+ alicesPrivate: Project;
17
+ acmeOrg: Project;
18
+ acmePublic: Project;
19
+ }
20
+ /**
21
+ * The primary tenant.
22
+ *
23
+ * **Alice is the org's `ownerId` but her membership row is `admin`.** Those are
24
+ * separate fields and this fixture deliberately makes them disagree, because
25
+ * production can too. Every org-role gate reads the **membership row**, so a
26
+ * test that treats `alice` as "the owner" will invert its own result and pass
27
+ * for the wrong reason — seed an explicit `role: 'owner'` member instead.
28
+ */
29
+ export declare function seedAcme(h: TestHarness): Promise<AcmeFixture>;
30
+ export interface BigClientFixture {
31
+ bigClient: Organization;
32
+ carol: SeededUser;
33
+ }
34
+ /**
35
+ * Second tenant. Carol is a member of BigClient — used for cross-org rejection
36
+ * scenarios (Carol acting in BigClient context cannot view Acme data).
37
+ */
38
+ export declare function seedBigClient(h: TestHarness): Promise<BigClientFixture>;
39
+ export interface ThirdOrgFixture {
40
+ initech: Organization;
41
+ dave: SeededUser;
42
+ }
43
+ /**
44
+ * Third tenant. Dave is a member of Initech — used for cross-org public-visibility
45
+ * scenarios (any authenticated user from any org can view a public project when
46
+ * cross-org public access is enabled).
47
+ */
48
+ export declare function seedThirdOrg(h: TestHarness): Promise<ThirdOrgFixture>;
49
+ export interface CommonsFixture {
50
+ commonsProject: Project;
51
+ alicesCommonsDef: SeededDefinition;
52
+ peter: SeededUser;
53
+ }
54
+ /**
55
+ * Commons-mode project (`autoJoinOnUpload=true`) inside Acme with Alice's
56
+ * pre-existing definition. Peter is a separate authenticated user — *not* a
57
+ * project member — who is allowed to upload his own new definitions but cannot
58
+ * touch Alice's existing one.
59
+ *
60
+ * Requires {@link seedAcme} to have run first (uses Alice as project owner +
61
+ * existing-def owner).
62
+ */
63
+ export declare function seedCommons(h: TestHarness, opts: {
64
+ acmeId: string;
65
+ aliceId: string;
66
+ }): Promise<CommonsFixture>;
67
+ //# sourceMappingURL=scenarios.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenarios.d.ts","sourceRoot":"","sources":["../../src/testing/scenarios.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE9E,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,UAAU,CAAC;IAClB,GAAG,EAAE,UAAU,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CA8BnE;AAED,MAAM,WAAW,gBAAgB;IAChC,SAAS,EAAE,YAAY,CAAC;IACxB,KAAK,EAAE,UAAU,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAS7E;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,UAAU,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAS3E;AAED,MAAM,WAAW,cAAc;IAC9B,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,KAAK,EAAE,UAAU,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAChC,CAAC,EAAE,WAAW,EACd,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvC,OAAO,CAAC,cAAc,CAAC,CAgBzB"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The shared cast the handler tests are written against.
3
+ *
4
+ * These compose only the seeders in `./harness.ts`, so they run on whatever
5
+ * provider stack the host built its {@link TestHarness} around. They live here
6
+ * rather than in a host's fixtures because a handler test that travels with its
7
+ * handler needs the same two tenants wherever it runs — a host that reseeds its
8
+ * own Acme with different roles gets different results from the same test.
9
+ */
10
+ import { seedDefinition, seedOrg, seedOrgMember, seedProject, seedUser } from './harness.js';
11
+ /**
12
+ * The primary tenant.
13
+ *
14
+ * **Alice is the org's `ownerId` but her membership row is `admin`.** Those are
15
+ * separate fields and this fixture deliberately makes them disagree, because
16
+ * production can too. Every org-role gate reads the **membership row**, so a
17
+ * test that treats `alice` as "the owner" will invert its own result and pass
18
+ * for the wrong reason — seed an explicit `role: 'owner'` member instead.
19
+ */
20
+ export async function seedAcme(h) {
21
+ const alice = await seedUser(h, 'alice@acme.test');
22
+ const bob = await seedUser(h, 'bob@acme.test');
23
+ const acme = await seedOrg(h, { name: 'Acme', slug: 'acme', ownerId: alice.id });
24
+ await seedOrgMember(h, { orgId: acme.id, userId: alice.id, role: 'admin' });
25
+ await seedOrgMember(h, { orgId: acme.id, userId: bob.id, role: 'member' });
26
+ const alicesPrivate = await seedProject(h, {
27
+ orgId: acme.id,
28
+ name: 'Alice Private',
29
+ slug: 'alice-private',
30
+ ownerId: alice.id,
31
+ visibility: 'private'
32
+ });
33
+ const acmeOrg = await seedProject(h, {
34
+ orgId: acme.id,
35
+ name: 'Acme Org Project',
36
+ slug: 'acme-org',
37
+ ownerId: alice.id,
38
+ visibility: 'org'
39
+ });
40
+ const acmePublic = await seedProject(h, {
41
+ orgId: acme.id,
42
+ name: 'Acme Public',
43
+ slug: 'acme-public',
44
+ ownerId: alice.id,
45
+ visibility: 'public'
46
+ });
47
+ return { acme, alice, bob, alicesPrivate, acmeOrg, acmePublic };
48
+ }
49
+ /**
50
+ * Second tenant. Carol is a member of BigClient — used for cross-org rejection
51
+ * scenarios (Carol acting in BigClient context cannot view Acme data).
52
+ */
53
+ export async function seedBigClient(h) {
54
+ const carol = await seedUser(h, 'carol@bigclient.test');
55
+ const bigClient = await seedOrg(h, {
56
+ name: 'BigClient',
57
+ slug: 'bigclient',
58
+ ownerId: carol.id
59
+ });
60
+ await seedOrgMember(h, { orgId: bigClient.id, userId: carol.id, role: 'member' });
61
+ return { bigClient, carol };
62
+ }
63
+ /**
64
+ * Third tenant. Dave is a member of Initech — used for cross-org public-visibility
65
+ * scenarios (any authenticated user from any org can view a public project when
66
+ * cross-org public access is enabled).
67
+ */
68
+ export async function seedThirdOrg(h) {
69
+ const dave = await seedUser(h, 'dave@initech.test');
70
+ const initech = await seedOrg(h, {
71
+ name: 'Initech',
72
+ slug: 'initech',
73
+ ownerId: dave.id
74
+ });
75
+ await seedOrgMember(h, { orgId: initech.id, userId: dave.id, role: 'member' });
76
+ return { initech, dave };
77
+ }
78
+ /**
79
+ * Commons-mode project (`autoJoinOnUpload=true`) inside Acme with Alice's
80
+ * pre-existing definition. Peter is a separate authenticated user — *not* a
81
+ * project member — who is allowed to upload his own new definitions but cannot
82
+ * touch Alice's existing one.
83
+ *
84
+ * Requires {@link seedAcme} to have run first (uses Alice as project owner +
85
+ * existing-def owner).
86
+ */
87
+ export async function seedCommons(h, opts) {
88
+ const peter = await seedUser(h, 'peter@elsewhere.test');
89
+ const commonsProject = await seedProject(h, {
90
+ orgId: opts.acmeId,
91
+ name: 'Acme Commons',
92
+ slug: 'acme-commons',
93
+ ownerId: opts.aliceId,
94
+ visibility: 'public',
95
+ autoJoinOnUpload: true
96
+ });
97
+ const alicesCommonsDef = await seedDefinition(h, {
98
+ projectId: commonsProject.id,
99
+ ownerId: opts.aliceId,
100
+ displayName: "Alice's Commons Def"
101
+ });
102
+ return { commonsProject, alicesCommonsDef, peter };
103
+ }
104
+ //# sourceMappingURL=scenarios.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenarios.js","sourceRoot":"","sources":["../../src/testing/scenarios.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The one thing test fixtures cannot get from `SelvaConfig`: creating and
3
+ * reading identities.
4
+ *
5
+ * Everything else a fixture needs — orgs, projects, definitions, invites —
6
+ * lives behind `IDataProvider` and is portable as-is. Identity is not.
7
+ * `IAuthProvider` exposes user *creation* only through the optional
8
+ * `passwordAuth.createUserWithPassword`, and a provider that authenticates by
9
+ * OAuth or forwarded headers implements none of it, so there is no interface
10
+ * method every provider answers. Each provider also stores identity its own
11
+ * way — the local one in `auth-users.json`, Supabase in its own `auth.users`
12
+ * table — and seeding has to reach that store directly, not through a login
13
+ * flow no test wants to perform.
14
+ *
15
+ * So the host supplies this. Two methods, because that is all the shared
16
+ * fixtures use: `seedUser` creates, `actAs` reads back.
17
+ */
18
+ import type { AuthUser } from '@selvajs/platform';
19
+ export interface SeedAuthAdapter {
20
+ /**
21
+ * Create an identity and return it. Called before the data-layer row exists,
22
+ * so implementations must not assume `ensureUser` has run.
23
+ *
24
+ * The returned `id` is what every other seeder keys off, so it has to be the
25
+ * id the auth provider will report for this user later — not a fresh one.
26
+ */
27
+ createUser(email: string): Promise<AuthUser>;
28
+ /** Read an identity back. `null` when absent, so `actAs` can fail loudly. */
29
+ findById(id: string): Promise<AuthUser | null>;
30
+ }
31
+ //# sourceMappingURL=seed-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-adapter.d.ts","sourceRoot":"","sources":["../../src/testing/seed-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,WAAW,eAAe;IAC/B;;;;;;OAMG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE7C,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;CAC/C"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The one thing test fixtures cannot get from `SelvaConfig`: creating and
3
+ * reading identities.
4
+ *
5
+ * Everything else a fixture needs — orgs, projects, definitions, invites —
6
+ * lives behind `IDataProvider` and is portable as-is. Identity is not.
7
+ * `IAuthProvider` exposes user *creation* only through the optional
8
+ * `passwordAuth.createUserWithPassword`, and a provider that authenticates by
9
+ * OAuth or forwarded headers implements none of it, so there is no interface
10
+ * method every provider answers. Each provider also stores identity its own
11
+ * way — the local one in `auth-users.json`, Supabase in its own `auth.users`
12
+ * table — and seeding has to reach that store directly, not through a login
13
+ * flow no test wants to perform.
14
+ *
15
+ * So the host supplies this. Two methods, because that is all the shared
16
+ * fixtures use: `seedUser` creates, `actAs` reads back.
17
+ */
18
+ export {};
19
+ //# sourceMappingURL=seed-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-adapter.js","sourceRoot":"","sources":["../../src/testing/seed-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG"}