Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | 1x 1x 14x 14x 1x 13x 1x 10x 10x 3x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 3x 3x 3x 3x 3x 3x 3x 3x 7x 7x 10x 1x 3x 3x 1x 2x 2x 2x 1x 1x | // Test Cleanup - Delete test data created by E2E tests
// ONLY available in non-production environments
import { Hono } from "hono";
import { getDb } from "../../db";
import * as schema from "../../db/schema";
import { like, eq, and, isNull, desc } from "drizzle-orm";
import { success, error } from "../../lib/response";
type Env = {
Bindings: CloudflareBindings;
};
const testCleanup = new Hono<Env>();
// Guard: block in production
testCleanup.use("*", async (c, next) => {
const env = c.env.ENVIRONMENT || "local";
if (env === "production") {
return error(c, "FORBIDDEN", "Test cleanup is not available in production", 403);
}
await next();
});
// DELETE /test-cleanup?emailPrefix=e2e-
// Deletes users, accounts, sessions, roles, and invitations matching the email prefix
testCleanup.delete("/", async (c) => {
const emailPrefix = c.req.query("emailPrefix");
if (!emailPrefix || emailPrefix.length < 8) {
return error(c, "BAD_REQUEST", "emailPrefix query param required (min 8 chars)", 400);
}
const db = getDb(c.env.DB);
const emailPattern = `${emailPrefix}%`;
// Find users matching prefix
const users = await db
.select({ id: schema.users.id, email: schema.users.email })
.from(schema.users)
.where(like(schema.users.email, emailPattern))
.all();
const userIds = users.map((u) => u.id);
let deletedUsers = 0;
let deletedRoles = 0;
let deletedInvitations = 0;
let deletedSessions = 0;
let deletedAccounts = 0;
// Delete related data for each user
for (const userId of userIds) {
// Delete sessions
const sessionResult = await db
.delete(schema.sessions)
.where(eq(schema.sessions.userId, userId))
.run();
deletedSessions += sessionResult.meta.changes ?? 0;
// Delete accounts
const accountResult = await db
.delete(schema.accounts)
.where(eq(schema.accounts.userId, userId))
.run();
deletedAccounts += accountResult.meta.changes ?? 0;
// Delete user_tenant_roles
const roleResult = await db
.delete(schema.userTenantRoles)
.where(eq(schema.userTenantRoles.userId, userId))
.run();
deletedRoles += roleResult.meta.changes ?? 0;
// Delete user
const userResult = await db
.delete(schema.users)
.where(eq(schema.users.id, userId))
.run();
deletedUsers += userResult.meta.changes ?? 0;
}
// Delete invitations matching email prefix
const invitationResult = await db
.delete(schema.teamInvitations)
.where(like(schema.teamInvitations.email, emailPattern))
.run();
deletedInvitations += invitationResult.meta.changes ?? 0;
return success(c, {
message: "Test cleanup complete",
deleted: {
users: deletedUsers,
sessions: deletedSessions,
accounts: deletedAccounts,
roles: deletedRoles,
invitations: deletedInvitations,
},
});
});
// GET /test-cleanup/invitation-token?email=...
// Look up the most recent pending invitation token for an email (for E2E test link navigation)
testCleanup.get("/invitation-token", async (c) => {
const email = c.req.query("email");
if (!email) {
return error(c, "BAD_REQUEST", "email query param required", 400);
}
const db = getDb(c.env.DB);
const invitation = await db
.select({
token: schema.teamInvitations.token,
proId: schema.teamInvitations.proId,
role: schema.teamInvitations.role,
expiresAt: schema.teamInvitations.expiresAt,
})
.from(schema.teamInvitations)
.where(
and(
eq(schema.teamInvitations.email, email.toLowerCase().trim()),
isNull(schema.teamInvitations.acceptedAt),
),
)
.orderBy(desc(schema.teamInvitations.dateCreated))
.get();
if (!invitation) {
return error(c, "NOT_FOUND", "No pending invitation found for this email", 404);
}
return success(c, { token: invitation.token });
});
export default testCleanup;
|