Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions client/packages/core/__tests__/src/auth-extra-fields.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { expect } from 'vitest';
import { i } from '../../src';
import { makeE2ETest, apiUrl } from './utils/e2e';

const schema = i.schema({
entities: {
$users: i.entity({
email: i.string().unique().indexed().optional(),
username: i.string().unique().indexed().optional(),
displayName: i.string().optional(),
}),
},
});

async function generateMagicCode(
appId: string,
adminToken: string,
email: string,
): Promise<string> {
const res = await fetch(`${apiUrl}/admin/magic_code`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'app-id': appId,
authorization: `Bearer ${adminToken}`,
},
body: JSON.stringify({ email }),
});
const data = await res.json();
return data.code;
Comment on lines +20 to +30
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add explicit HTTP status assertions before JSON parsing.

Line 20, Line 94, and Line 118 parse JSON unconditionally. If backend/auth/deploy is misconfigured, failures become noisy parse errors instead of clear request failures.

🔧 Suggested patch
 async function generateMagicCode(
   appId: string,
   adminToken: string,
   email: string,
 ): Promise<string> {
   const res = await fetch(`${apiUrl}/admin/magic_code`, {
@@
     body: JSON.stringify({ email }),
   });
+  if (!res.ok) {
+    throw new Error(`/admin/magic_code failed (${res.status}): ${await res.text()}`);
+  }
   const data = await res.json();
   return data.code;
 }
@@
     const res = await fetch(
       `${apiUrl}/admin/verify_magic_code?app_id=${appId}`,
       {
@@
       },
     );
+    if (!res.ok) {
+      throw new Error(
+        `/admin/verify_magic_code failed (${res.status}): ${await res.text()}`,
+      );
+    }
     const data = await res.json();
@@
     const res2 = await fetch(
       `${apiUrl}/admin/verify_magic_code?app_id=${appId}`,
       {
@@
       },
     );
+    if (!res2.ok) {
+      throw new Error(
+        `/admin/verify_magic_code failed (${res2.status}): ${await res2.text()}`,
+      );
+    }
     const data2 = await res2.json();

Also applies to: 94-109, 118-129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/packages/core/__tests__/src/auth-extra-fields.e2e.test.ts` around
lines 20 - 30, The test currently calls fetch and immediately does await
res.json() (using variables apiUrl, adminToken, appId, res, data) which hides
HTTP failures as JSON parse errors; update each such block (the POST to
`${apiUrl}/admin/magic_code` and the other occurrences around lines ~94 and
~118) to first assert the response status (e.g., check res.ok or specific status
codes) and if not OK throw or fail the test with a clear message including
res.status and await res.text() (or the parsed error body) before attempting
res.json(), so failures surface as explicit HTTP errors rather than parse
exceptions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitty / not-neccesary

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nezaj, understood — acknowledged and moving on!


🧠 Learnings used
Learnt from: CR
Repo: instantdb/instant PR: 0
File: client/packages/create-instant-app/template/rules/AGENTS.md:0-0
Timestamp: 2026-03-20T22:27:09.580Z
Learning: Applies to client/packages/create-instant-app/template/rules/**/*.{ts,tsx,js,jsx} : Pass `extraFields` to sign-in methods to set custom properties on $users atomically during signup. Fields must be optional attributes on $users in schema.

Learnt from: dwwoelfel
Repo: instantdb/instant PR: 2329
File: client/packages/resumable-stream/__tests__/src/testing-stream.ts:6-13
Timestamp: 2026-02-26T20:42:24.553Z
Learning: In the instantdb/instant repository, test helper utilities under any __tests__ directories may implement simpler logic compared to production code. Apply this learning to review test utilities in client/packages/resumable-stream/__tests__/src/testing-stream.ts by prioritizing correctness for test scenarios while avoiding unnecessary production-grade edge-case handling. This guidance applies broadly to test helper files (TypeScript tests) and should not constrain production code paths.

}

const authTest = makeE2ETest({ schema });

authTest(
'new user with extraFields gets fields written and created=true',
async ({ db, appId, adminToken }) => {
const email = `new-${Date.now()}@test.com`;

const code = await generateMagicCode(appId, adminToken, email);
const res = await db.auth.signInWithMagicCode({
email,
code,
extraFields: { username: 'cool_user', displayName: 'Cool User' },
});

expect(res.created).toBe(true);

const { data } = await db.queryOnce({ $users: {} });
const user = data.$users.find((u: any) => u.email === email);
expect(user).toBeDefined();
expect(user!.username).toBe('cool_user');
expect(user!.displayName).toBe('Cool User');
},
);

authTest(
'returning user gets created=false',
async ({ db, appId, adminToken }) => {
const email = `returning-${Date.now()}@test.com`;

// First sign in -- creates user
const code1 = await generateMagicCode(appId, adminToken, email);
const res1 = await db.auth.signInWithMagicCode({ email, code: code1 });
expect(res1.created).toBe(true);

// Second sign in -- existing user
const code2 = await generateMagicCode(appId, adminToken, email);
const res2 = await db.auth.signInWithMagicCode({ email, code: code2 });
expect(res2.created).toBe(false);
},
);

authTest(
'sign in without extraFields works (backwards compat)',
async ({ db, appId, adminToken }) => {
const email = `compat-${Date.now()}@test.com`;

const code = await generateMagicCode(appId, adminToken, email);
const res = await db.auth.signInWithMagicCode({ email, code });

expect(res.user).toBeDefined();
expect(res.user.email).toBe(email);
},
);

authTest(
'admin verify_magic_code returns { user, created } for checkMagicCode',
async ({ db: _db, appId, adminToken }) => {
const email = `admin-consume-${Date.now()}@test.com`;
const code = await generateMagicCode(appId, adminToken, email);

// Hit the admin endpoint directly (same as admin SDK checkMagicCode)
const res = await fetch(
`${apiUrl}/admin/verify_magic_code?app_id=${appId}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
authorization: `Bearer ${adminToken}`,
},
body: JSON.stringify({
email,
code,
'extra-fields': { username: 'admin_user' },
}),
},
);
const data = await res.json();

// Response should have user nested (not splatted) and created flag
expect(data.user).toBeDefined();
expect(data.user.email).toBe(email);
expect(data.created).toBe(true);

// Second call -- existing user
const code2 = await generateMagicCode(appId, adminToken, email);
const res2 = await fetch(
`${apiUrl}/admin/verify_magic_code?app_id=${appId}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
authorization: `Bearer ${adminToken}`,
},
body: JSON.stringify({ email, code: code2 }),
},
);
const data2 = await res2.json();

expect(data2.user).toBeDefined();
expect(data2.created).toBe(false);
},
);
2 changes: 1 addition & 1 deletion client/packages/version/src/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
// Update the version here and merge your code to main to
// publish a new version of all of the packages to npm.

const version = 'v0.22.167';
const version = 'v0.22.168';

export { version };
Loading