Fix sequencer operator mismatch error

This commit is contained in:
Konstantin Wohlwend 2026-04-09 23:30:20 -07:00
parent 9e342da0f2
commit 2d34e4b84e
3 changed files with 81 additions and 1 deletions

View File

@ -153,7 +153,7 @@ async function backfillSequenceIds(batchSize: number): Promise<boolean> {
WHERE "VerificationCode"."projectId" = changed_teams."projectId"
AND "VerificationCode"."branchId" = changed_teams."branchId"
AND "VerificationCode"."type" = 'TEAM_INVITATION'
AND "VerificationCode"."data"->>'team_id' = changed_teams."teamId"
AND "VerificationCode"."data"->>'team_id' = changed_teams."teamId"::text
AND "VerificationCode"."shouldUpdateSequenceId" = FALSE
`;
}

View File

@ -1173,6 +1173,83 @@ describe.sequential('External DB Sync - Basic Tests', () => {
await waitForSyncedTeamInvitationDeletion(client, invitationId);
}, TEST_TIMEOUT);
/**
* What it does:
* - Sends a team invitation, renames the team, and verifies team_display_name updates externally.
*
* Why it matters:
* - Covers the sequencer cascade that marks TEAM_INVITATION rows for re-sync on team updates.
*/
test('TeamInvitation sync updates display name after team rename (Postgres)', async () => {
const dbName = 'team_invitation_team_rename_test';
const connectionString = await dbManager.createDatabase(dbName);
await createProjectWithExternalDb({
main: {
type: 'postgres',
connectionString,
}
}, { display_name: 'Invitation Rename Test Project' });
const client = dbManager.getClient(dbName);
const initialTeamName = 'Invitation Team Before Rename';
const updatedTeamName = 'Invitation Team After Rename';
const invitedEmail = `team-rename-${randomUUID()}@example.com`;
const createTeamResponse = await niceBackendFetch('/api/v1/teams', {
accessType: 'admin',
method: 'POST',
body: { display_name: initialTeamName },
});
expect(createTeamResponse.status).toBe(201);
const teamId = createTeamResponse.body.id;
const inviteResponse = await niceBackendFetch('/api/v1/team-invitations/send-code', {
accessType: 'admin',
method: 'POST',
body: { team_id: teamId, email: invitedEmail, callback_url: 'http://localhost:12345/callback' },
});
expect(inviteResponse.status).toBe(200);
await waitForSyncedTeamInvitation(client, invitedEmail);
const initialInvitation = await client.query(
`SELECT "team_display_name" FROM "team_invitations" WHERE "recipient_email" = $1`,
[invitedEmail],
);
expect(initialInvitation.rows.length).toBe(1);
expect(initialInvitation.rows[0].team_display_name).toBe(initialTeamName);
const updateTeamResponse = await niceBackendFetch(`/api/v1/teams/${teamId}`, {
accessType: 'admin',
method: 'PATCH',
body: { display_name: updatedTeamName },
});
expect(updateTeamResponse.status).toBe(200);
await waitForCondition(
async () => {
const updatedInvitation = await client.query(
`SELECT "team_display_name" FROM "team_invitations" WHERE "recipient_email" = $1`,
[invitedEmail],
);
return updatedInvitation.rows.length === 1 && updatedInvitation.rows[0].team_display_name === updatedTeamName;
},
{
timeoutMs: 180_000,
intervalMs: 500,
description: `team invitation for ${invitedEmail} to reflect renamed team`,
},
);
const finalInvitation = await client.query(
`SELECT "team_display_name" FROM "team_invitations" WHERE "recipient_email" = $1`,
[invitedEmail],
);
expect(finalInvitation.rows.length).toBe(1);
expect(finalInvitation.rows[0].team_display_name).toBe(updatedTeamName);
}, TEST_TIMEOUT);
/**
* What it does:
* - Sends a team invitation, queries ClickHouse analytics API to verify.

View File

@ -170,3 +170,6 @@ A: In `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/pag
Q: Why can restricted users appear logged out on auth handler pages even with a valid session?
A: `useUser()` filters out restricted users by default. In `packages/template/src/components-page/auth-page.tsx`, use `useUser({ includeRestricted: true })` and explicitly redirect restricted users to onboarding when `automaticRedirect` is enabled.
Q: Why can external-db-sync sequencer throw `operator does not exist: text = uuid` on team updates?
A: In `apps/backend/src/app/api/latest/internal/external-db-sync/sequencer/route.ts`, the TEAM_INVITATION cascade compares JSON text (`"VerificationCode"."data"->>'team_id'`) against `"Team"."teamId"` (`uuid`). Cast the UUID side to text (`changed_teams."teamId"::text`) in the WHERE clause so Postgres type resolution succeeds and team-invitation re-sync marking works.