@specific.dev/spectest 0.45.0 → 0.46.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/spectest",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
4
4
  "description": "Spectest SDK for defining test environments in TypeScript.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,261 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { mapSupabaseConfig } from "./supabase-config.js";
4
+
5
+ const toml = (s: string): unknown =>
6
+ (globalThis as unknown as { Bun: { TOML: { parse(s: string): unknown } } }).Bun.TOML.parse(s);
7
+
8
+ describe("mapSupabaseConfig", () => {
9
+ test("maps the auth settings a test can observe", () => {
10
+ const out = mapSupabaseConfig(
11
+ toml(`
12
+ [auth]
13
+ jwt_expiry = 7200
14
+ minimum_password_length = 10
15
+
16
+ [auth.email]
17
+ otp_length = 8
18
+
19
+ [auth.hook.before_user_created]
20
+ enabled = true
21
+ uri = "pg-functions://postgres/public/hook_restrict_signup"
22
+ `),
23
+ );
24
+
25
+ expect(out.auth).toMatchObject({
26
+ GOTRUE_JWT_EXP: "7200",
27
+ GOTRUE_PASSWORD_MIN_LENGTH: "10",
28
+ GOTRUE_MAILER_OTP_LENGTH: "8",
29
+ GOTRUE_HOOK_BEFORE_USER_CREATED_ENABLED: "true",
30
+ GOTRUE_HOOK_BEFORE_USER_CREATED_URI: "pg-functions://postgres/public/hook_restrict_signup",
31
+ });
32
+ expect(out.warnings).toEqual([]);
33
+ });
34
+
35
+ // The two mappings a copy-paste gets backwards without anyone noticing.
36
+ test("negates enable_signup and enable_confirmations", () => {
37
+ const on = mapSupabaseConfig(
38
+ toml(`
39
+ [auth]
40
+ enable_signup = true
41
+ [auth.email]
42
+ enable_confirmations = true
43
+ `),
44
+ );
45
+ expect(on.auth.GOTRUE_DISABLE_SIGNUP).toBe("false");
46
+ expect(on.auth.GOTRUE_MAILER_AUTOCONFIRM).toBe("false");
47
+
48
+ const off = mapSupabaseConfig(
49
+ toml(`
50
+ [auth]
51
+ enable_signup = false
52
+ [auth.email]
53
+ enable_confirmations = false
54
+ `),
55
+ );
56
+ expect(off.auth.GOTRUE_DISABLE_SIGNUP).toBe("true");
57
+ expect(off.auth.GOTRUE_MAILER_AUTOCONFIRM).toBe("true");
58
+ });
59
+
60
+ test("collects mail templates by id", () => {
61
+ const out = mapSupabaseConfig(
62
+ toml(`
63
+ [auth.email.template.invite]
64
+ subject = "You've been invited"
65
+ content_path = "./supabase/templates/invite.html"
66
+ `),
67
+ );
68
+ expect(out.templates.invite).toEqual({
69
+ path: "./supabase/templates/invite.html",
70
+ subject: "You've been invited",
71
+ });
72
+ });
73
+
74
+ // A subject with no content_path is how the CLI spells "default body, custom
75
+ // subject" — it must not invent a template path.
76
+ test("keeps a subject-only template out of the served set", () => {
77
+ const out = mapSupabaseConfig(
78
+ toml(`
79
+ [auth.email.template.recovery]
80
+ subject = "Reset it"
81
+ content_path = ""
82
+ `),
83
+ );
84
+ expect(out.templates.recovery).toEqual({ path: "", subject: "Reset it" });
85
+ });
86
+
87
+ test("reads per-function verify_jwt and the api schemas", () => {
88
+ const out = mapSupabaseConfig(
89
+ toml(`
90
+ [api]
91
+ schemas = ["public", "graphql_public"]
92
+ max_rows = 500
93
+ port = 54321
94
+
95
+ [functions.invoice-api]
96
+ verify_jwt = false
97
+ [functions.account-api]
98
+ verify_jwt = true
99
+ `),
100
+ );
101
+ expect(out.functionVerifyJwt).toEqual({ "invoice-api": false, "account-api": true });
102
+ expect(out.rest).toEqual({ PGRST_DB_SCHEMAS: "public,graphql_public", PGRST_DB_MAX_ROWS: "500" });
103
+ });
104
+
105
+ // Addresses are this environment's, not the file's: its URLs name the
106
+ // developer's machine, where nothing is listening inside a VM.
107
+ test("never takes site_url or the redirect allow list from the file", () => {
108
+ const out = mapSupabaseConfig(
109
+ toml(`
110
+ [auth]
111
+ site_url = "http://localhost:3000"
112
+ additional_redirect_urls = ["http://localhost:3000/**"]
113
+ `),
114
+ );
115
+ expect(out.auth).toEqual({});
116
+ expect(out.unsupported).toEqual([]);
117
+ expect(out.warnings).toEqual([]);
118
+ });
119
+
120
+ // Reported, never fatal — the file belongs to the project and describes more
121
+ // environments than this one.
122
+ test("reports a setting it cannot apply, with the reason", () => {
123
+ const out = mapSupabaseConfig(
124
+ toml(`
125
+ [auth.captcha]
126
+ enabled = true
127
+ provider = "hcaptcha"
128
+ secret = "env(HCAPTCHA_SECRET)"
129
+ `),
130
+ );
131
+ expect(out.unsupported).toHaveLength(1);
132
+ expect(out.unsupported[0]!.key).toBe("auth.captcha");
133
+ expect(out.unsupported[0]!.reason).toMatch(/captcha/);
134
+ });
135
+
136
+ // Social sign-in is collected, not refused: the component serves each
137
+ // provider it has an emulator for at that provider's real endpoints.
138
+ test("collects the social providers and where their credentials come from", () => {
139
+ const out = mapSupabaseConfig(
140
+ toml(`
141
+ [auth.external.google]
142
+ enabled = true
143
+ client_id = "env(GOOGLE_OAUTH_CLIENT_ID)"
144
+ secret = "env(GOOGLE_OAUTH_SECRET)"
145
+
146
+ [auth.external.azure]
147
+ enabled = true
148
+ client_id = "literal-azure-id"
149
+ url = "https://login.microsoftonline.com/tenant-1/v2.0"
150
+
151
+ [auth.external.facebook]
152
+ enabled = false
153
+ `),
154
+ );
155
+ expect(out.externalProviders.google).toEqual({
156
+ enabled: true,
157
+ clientIdFrom: "GOOGLE_OAUTH_CLIENT_ID",
158
+ secretFrom: "GOOGLE_OAUTH_SECRET",
159
+ });
160
+ expect(out.externalProviders.azure).toEqual({
161
+ enabled: true,
162
+ clientId: "literal-azure-id",
163
+ url: "https://login.microsoftonline.com/tenant-1/v2.0",
164
+ });
165
+ expect(out.externalProviders.facebook!.enabled).toBe(false);
166
+ expect(out.unsupported).toEqual([]);
167
+ });
168
+
169
+ // The common shape: a config lists a dozen providers with every one off.
170
+ // Nothing is lost by not applying them, so nothing is said.
171
+ test("stays quiet about an unsupported feature that is switched off", () => {
172
+ const out = mapSupabaseConfig(
173
+ toml(`
174
+ [auth.captcha]
175
+ enabled = false
176
+ provider = "hcaptcha"
177
+
178
+ [auth.sms]
179
+ enable_signup = false
180
+ template = "code {{ .Code }}"
181
+ `),
182
+ );
183
+ expect(out.unsupported).toEqual([]);
184
+ });
185
+
186
+ test("reports a section where only one feature is enabled", () => {
187
+ const out = mapSupabaseConfig(
188
+ toml(`
189
+ [auth.mfa.phone]
190
+ enroll_enabled = true
191
+ verify_enabled = false
192
+ `),
193
+ );
194
+ expect(out.unsupported.map((u) => u.key)).toEqual(["auth.mfa.phone"]);
195
+ });
196
+
197
+ // Both ways the file defers a value to the surrounding environment. Passing
198
+ // either through would hand a function the string `env(NAME)` or a base64
199
+ // ciphertext — the latter looking enough like a value to go unnoticed.
200
+ test("separates literal secrets from the two deferred forms", () => {
201
+ const out = mapSupabaseConfig(
202
+ toml(`
203
+ [edge_runtime.secrets]
204
+ FEATURE_FLAG = "true"
205
+ STRIPE_KEY = "env(STRIPE_SECRET_KEY)"
206
+ SENDGRID_KEY = "encrypted:c2VjcmV0LXZhbHVl"
207
+ `),
208
+ );
209
+ expect(out.functionSecrets).toEqual({ FEATURE_FLAG: "true" });
210
+ // `env(NAME)` records the name to read it from…
211
+ expect(out.deferredSecrets).toEqual({ STRIPE_KEY: "STRIPE_SECRET_KEY" });
212
+ // …while an inline ciphertext names nothing and cannot be supplied here.
213
+ expect(out.encryptedValues).toEqual(["edge_runtime.secrets.SENDGRID_KEY"]);
214
+ });
215
+
216
+ // A provider credential reads from the same `.env` as everything else — it
217
+ // is a field on the project's auth settings rather than a named secret, but
218
+ // the value gets there the same way.
219
+ test("records where a provider credential is read from", () => {
220
+ const out = mapSupabaseConfig(
221
+ toml(`
222
+ [auth.external.google]
223
+ enabled = true
224
+ client_id = "env(GOOGLE_OAUTH_CLIENT_ID)"
225
+ secret = "encrypted:c2hoaA=="
226
+ `),
227
+ );
228
+ expect(out.externalProviders.google).toEqual({
229
+ enabled: true,
230
+ clientIdFrom: "GOOGLE_OAUTH_CLIENT_ID",
231
+ });
232
+ // The ciphertext names nothing, so it is reported instead of guessed at.
233
+ expect(out.encryptedValues).toEqual(["auth.external.google.secret"]);
234
+ });
235
+
236
+ test("warns rather than fails on a key it has never seen", () => {
237
+ const out = mapSupabaseConfig(toml(`\n[auth]\nfuture_option = true\n`));
238
+ expect(out.warnings).toEqual(["auth.future_option"]);
239
+ });
240
+
241
+ // Sections spectest owns outright never reach the user as noise.
242
+ test("drops the topology sections in silence", () => {
243
+ const out = mapSupabaseConfig(
244
+ toml(`
245
+ project_id = "test"
246
+ [db]
247
+ port = 54322
248
+ [db.pooler]
249
+ enabled = true
250
+ [studio]
251
+ port = 54323
252
+ [inbucket]
253
+ port = 54324
254
+ [remotes.production.auth]
255
+ site_url = "https://example.com"
256
+ `),
257
+ );
258
+ expect(out.warnings).toEqual([]);
259
+ expect(out.auth).toEqual({});
260
+ });
261
+ });