Skip to content

Commit 48c2d45

Browse files
Bill Leoutsakoscursoragent
authored andcommitted
Add validated E2E persona world model
Model settings personas as deterministic, serializable resource graphs and provide production-first factories with exact-ID cleanup so impossible or cross-world fixture states fail before browser tests run. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e8f3bfe commit 48c2d45

16 files changed

Lines changed: 2751 additions & 0 deletions

apps/sim/e2e/fixtures/cleanup.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { db } from '@sim/db'
2+
import {
3+
auditLog,
4+
invitation,
5+
invitationWorkspaceGrant,
6+
member,
7+
organization,
8+
permissionGroup,
9+
permissionGroupMember,
10+
permissionGroupWorkspace,
11+
permissions,
12+
settings,
13+
subscription,
14+
user,
15+
userStats,
16+
workspace,
17+
} from '@sim/db/schema'
18+
import { inArray, or } from 'drizzle-orm'
19+
import type { ScenarioManifest } from './e2e-world'
20+
21+
/**
22+
* Deletes only IDs recorded by a successful seed. Names and prefixes are never used as delete
23+
* predicates. Identity verification makes a stale or hand-edited manifest fail closed.
24+
*/
25+
export async function cleanupSeededWorld(manifest: ScenarioManifest): Promise<void> {
26+
const userIdentities = Object.values(manifest.worlds).flatMap((world) =>
27+
Object.values(world.userIdentities)
28+
)
29+
const organizationIdentities = Object.values(manifest.worlds).flatMap((world) =>
30+
Object.values(world.organizationIdentities)
31+
)
32+
const workspaceIdentities = Object.values(manifest.worlds).flatMap((world) =>
33+
Object.values(world.workspaceIdentities)
34+
)
35+
await verifyOwnership(userIdentities, organizationIdentities, workspaceIdentities)
36+
37+
const userIds = userIdentities.map(({ id }) => id)
38+
const organizationIds = organizationIdentities.map(({ id }) => id)
39+
const workspaceIds = workspaceIdentities.map(({ id }) => id)
40+
const resourceIds = [
41+
...workspaceIds,
42+
...organizationIds,
43+
...Object.values(manifest.personas).flatMap(({ permissionGroupIds }) => permissionGroupIds),
44+
]
45+
const subscriptionIds = collectValues(manifest, 'subscriptionIds')
46+
const permissionIds = collectValues(manifest, 'permissionIds')
47+
const permissionGroupIds = collectValues(manifest, 'permissionGroupIds')
48+
const permissionGroupMemberIds = collectValues(manifest, 'permissionGroupMemberIds')
49+
const invitationIds = collectValues(manifest, 'invitationIds')
50+
const invitationGrantIds = collectValues(manifest, 'invitationGrantIds')
51+
const organizationMemberIds = collectValues(manifest, 'organizationMemberIds')
52+
53+
await db.transaction(async (tx) => {
54+
const auditPredicates = [
55+
workspaceIds.length ? inArray(auditLog.workspaceId, workspaceIds) : undefined,
56+
userIds.length ? inArray(auditLog.actorId, userIds) : undefined,
57+
resourceIds.length ? inArray(auditLog.resourceId, resourceIds) : undefined,
58+
].filter((value): value is NonNullable<typeof value> => value !== undefined)
59+
if (auditPredicates.length > 0) await tx.delete(auditLog).where(or(...auditPredicates))
60+
61+
if (invitationGrantIds.length) {
62+
await tx
63+
.delete(invitationWorkspaceGrant)
64+
.where(inArray(invitationWorkspaceGrant.id, invitationGrantIds))
65+
}
66+
if (invitationIds.length) {
67+
await tx.delete(invitation).where(inArray(invitation.id, invitationIds))
68+
}
69+
if (permissionGroupMemberIds.length) {
70+
await tx
71+
.delete(permissionGroupMember)
72+
.where(inArray(permissionGroupMember.id, permissionGroupMemberIds))
73+
}
74+
if (permissionGroupIds.length) {
75+
await tx
76+
.delete(permissionGroupWorkspace)
77+
.where(inArray(permissionGroupWorkspace.permissionGroupId, permissionGroupIds))
78+
await tx.delete(permissionGroup).where(inArray(permissionGroup.id, permissionGroupIds))
79+
}
80+
if (permissionIds.length) {
81+
await tx.delete(permissions).where(inArray(permissions.id, permissionIds))
82+
}
83+
if (workspaceIds.length) {
84+
await tx.delete(workspace).where(inArray(workspace.id, workspaceIds))
85+
}
86+
if (organizationMemberIds.length) {
87+
await tx.delete(member).where(inArray(member.id, organizationMemberIds))
88+
}
89+
if (subscriptionIds.length) {
90+
await tx.delete(subscription).where(inArray(subscription.id, subscriptionIds))
91+
}
92+
if (organizationIds.length) {
93+
await tx.delete(organization).where(inArray(organization.id, organizationIds))
94+
}
95+
if (userIds.length) {
96+
await tx.delete(settings).where(inArray(settings.userId, userIds))
97+
await tx.delete(userStats).where(inArray(userStats.userId, userIds))
98+
await tx.delete(user).where(inArray(user.id, userIds))
99+
}
100+
})
101+
}
102+
103+
async function verifyOwnership(
104+
expectedUsers: Array<{ id: string; email: string; name: string }>,
105+
expectedOrganizations: Array<{ id: string; name: string; slug: string }>,
106+
expectedWorkspaces: Array<{ id: string; name: string }>
107+
): Promise<void> {
108+
const actualUsers = expectedUsers.length
109+
? await db
110+
.select({ id: user.id, email: user.email, name: user.name })
111+
.from(user)
112+
.where(
113+
inArray(
114+
user.id,
115+
expectedUsers.map(({ id }) => id)
116+
)
117+
)
118+
: []
119+
const actualOrganizations = expectedOrganizations.length
120+
? await db
121+
.select({ id: organization.id, name: organization.name, slug: organization.slug })
122+
.from(organization)
123+
.where(
124+
inArray(
125+
organization.id,
126+
expectedOrganizations.map(({ id }) => id)
127+
)
128+
)
129+
: []
130+
const actualWorkspaces = expectedWorkspaces.length
131+
? await db
132+
.select({ id: workspace.id, name: workspace.name })
133+
.from(workspace)
134+
.where(
135+
inArray(
136+
workspace.id,
137+
expectedWorkspaces.map(({ id }) => id)
138+
)
139+
)
140+
: []
141+
142+
assertExactIdentities('user', expectedUsers, actualUsers)
143+
assertExactIdentities('organization', expectedOrganizations, actualOrganizations)
144+
assertExactIdentities('workspace', expectedWorkspaces, actualWorkspaces)
145+
}
146+
147+
function assertExactIdentities<T extends { id: string }>(
148+
label: string,
149+
expected: T[],
150+
actual: T[]
151+
): void {
152+
const actualById = new Map(actual.map((value) => [value.id, value]))
153+
for (const value of expected) {
154+
if (JSON.stringify(actualById.get(value.id)) !== JSON.stringify(value)) {
155+
throw new Error(`Refusing cleanup: ${label} ownership mismatch for exact ID ${value.id}`)
156+
}
157+
}
158+
}
159+
160+
function collectValues(
161+
manifest: ScenarioManifest,
162+
key:
163+
| 'subscriptionIds'
164+
| 'permissionIds'
165+
| 'permissionGroupIds'
166+
| 'permissionGroupMemberIds'
167+
| 'invitationIds'
168+
| 'invitationGrantIds'
169+
| 'organizationMemberIds'
170+
): string[] {
171+
return Object.values(manifest.worlds).flatMap((world) => Object.values(world[key]))
172+
}

0 commit comments

Comments
 (0)