Skip to content

Commit df7d505

Browse files
committed
fix(files): align lifecycle fixtures and conflict responses
1 parent 5089d12 commit df7d505

3 files changed

Lines changed: 56 additions & 29 deletions

File tree

‎apps/sim/app/api/v1/admin/workspaces/[id]/members/[memberId]/route.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,10 +310,8 @@ export const DELETE = withRouteHandler(
310310
workspaceId,
311311
})
312312
} catch (error) {
313-
if (
314-
error instanceof WorkspaceBillingAccountRemovalError ||
315-
error instanceof ProjectConflictError
316-
) {
313+
if (error instanceof ProjectConflictError) return conflictResponse(error.message)
314+
if (error instanceof WorkspaceBillingAccountRemovalError) {
317315
return badRequestResponse(error.message)
318316
}
319317
logger.error('Admin API: Failed to remove workspace member', { error, workspaceId, memberId })

‎apps/sim/app/api/v1/admin/workspaces/[id]/members/route.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,10 +419,8 @@ export const DELETE = withRouteHandler(
419419

420420
return singleResponse({ removed: true, userId, workspaceId })
421421
} catch (error) {
422-
if (
423-
error instanceof WorkspaceBillingAccountRemovalError ||
424-
error instanceof ProjectConflictError
425-
) {
422+
if (error instanceof ProjectConflictError) return conflictResponse(error.message)
423+
if (error instanceof WorkspaceBillingAccountRemovalError) {
426424
return badRequestResponse(error.message)
427425
}
428426
logger.error('Admin API: Failed to remove workspace member', {

‎apps/sim/lib/billing/storage/project-accounting.integration.ts‎

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,22 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
109109
entity_id text NOT NULL, permission_type text NOT NULL);
110110
CREATE TABLE permission_group (id text PRIMARY KEY, organization_id text,
111111
config jsonb, updated_at timestamp);
112-
CREATE TABLE workspace_files (id text PRIMARY KEY, project_id text,
113-
workspace_id text, context text NOT NULL, size_bytes bigint, deleted_at timestamp);
114-
CREATE TABLE workspace_file_version (id text PRIMARY KEY,
115-
file_id text REFERENCES workspace_files(id) ON DELETE CASCADE, size_bytes bigint NOT NULL);
112+
CREATE TABLE "user" (id text PRIMARY KEY);
113+
CREATE TABLE folder (LIKE public.folder INCLUDING DEFAULTS, PRIMARY KEY (id));
114+
CREATE TABLE workspace_files (LIKE public.workspace_files INCLUDING DEFAULTS, PRIMARY KEY (id));
115+
ALTER TABLE workspace_files
116+
ALTER COLUMN user_id SET DEFAULT 'user-a',
117+
ALTER COLUMN key SET DEFAULT 'fixture-key',
118+
ALTER COLUMN original_name SET DEFAULT 'fixture.txt',
119+
ALTER COLUMN content_type SET DEFAULT 'text/plain';
120+
CREATE TABLE workspace_file_version (LIKE public.workspace_file_version INCLUDING DEFAULTS,
121+
PRIMARY KEY (id), FOREIGN KEY (file_id) REFERENCES workspace_files(id) ON DELETE CASCADE,
122+
UNIQUE (file_id, version));
123+
ALTER TABLE workspace_file_version
124+
ALTER COLUMN version SET DEFAULT 1,
125+
ALTER COLUMN key SET DEFAULT 'history-key',
126+
ALTER COLUMN content_type SET DEFAULT 'text/plain',
127+
ALTER COLUMN source SET DEFAULT 'unknown';
116128
CREATE TABLE knowledge_base (id text PRIMARY KEY, workspace_id text);
117129
CREATE TABLE document (id text PRIMARY KEY, knowledge_base_id text, connector_id text,
118130
file_size bigint, deleted_at timestamp);
@@ -123,7 +135,8 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
123135

124136
beforeEach(async () => {
125137
vi.stubEnv('FREE_STORAGE_LIMIT_GB', '1')
126-
await sql`TRUNCATE workspace_file_version, workspace_files, project, workspace, organization, user_stats, member, subscription, project_workspace, permissions, permission_group`
138+
await sql`TRUNCATE "user", folder, workspace_file_version, workspace_files, project, workspace, organization, user_stats, member, subscription, project_workspace, permissions, permission_group`
139+
await sql`INSERT INTO "user" (id) VALUES ('user-a'), ('user-b')`
127140
await sql`INSERT INTO organization (id) VALUES ('organization-a'), ('organization-b')`
128141
await sql`INSERT INTO user_stats (id, user_id) VALUES ('stats-a', 'user-a'), ('stats-b', 'user-b')`
129142
await sql`INSERT INTO project (id, owner_id, organization_id)
@@ -301,19 +314,32 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
301314
await releaseWrite.promise
302315
})
303316
await Promise.race([written.promise, write])
304-
const teardown = database.transaction(async (tx) => {
305-
const [connection] = await tx.execute<{ pid: number }>(
306-
query`SELECT pg_backend_pid() AS pid`
317+
const teardown = database
318+
.transaction(async (tx) => {
319+
const [connection] = await tx.execute<{ pid: number }>(
320+
query`SELECT pg_backend_pid() AS pid`
321+
)
322+
teardownPid.resolve(connection.pid)
323+
await prepareProjectsForAccountDeletion(tx, 'user-a', [])
324+
})
325+
.then(
326+
() => null,
327+
(error: unknown) => error
307328
)
308-
teardownPid.resolve(connection.pid)
309-
await prepareProjectsForAccountDeletion(tx, 'user-a', [])
310-
})
311329
try {
312-
await waitForDatabaseLock(await teardownPid.promise)
330+
await Promise.race([
331+
waitForDatabaseLock(await teardownPid.promise),
332+
teardown.then((error) => {
333+
if (error) throw error
334+
throw new Error('Account teardown completed before waiting for the Project write')
335+
}),
336+
])
313337
} finally {
314338
releaseWrite.resolve()
339+
await Promise.all([write, teardown])
315340
}
316-
await Promise.all([write, teardown])
341+
const teardownError = await teardown
342+
if (teardownError) throw teardownError
317343
expect(
318344
await sql`SELECT user_id, storage_used_bytes::integer AS bytes FROM user_stats ORDER BY user_id`
319345
).toEqual([
@@ -323,6 +349,9 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
323349
expect((await sql`SELECT owner_id FROM project WHERE id = 'project-a'`)[0].owner_id).toBe(
324350
'user-b'
325351
)
352+
expect(
353+
(await sql`SELECT user_id FROM workspace_files WHERE id = 'project-file'`)[0].user_id
354+
).toBe('user-b')
326355
expect(
327356
(await sql`SELECT billed_account_user_id FROM workspace WHERE id = 'workspace-a'`)[0]
328357
.billed_account_user_id
@@ -404,9 +433,11 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
404433
await database.transaction(async (tx) => {
405434
const prepared = await prepareProjectStorageMutationInTx(tx, context())
406435
await tx.execute(
407-
query`INSERT INTO workspace_files VALUES ('file', 'project-a', NULL, 'project', 100, NULL)`
436+
query`INSERT INTO workspace_files (id, project_id, workspace_id, context, size_bytes, deleted_at) VALUES ('file', 'project-a', NULL, 'project', 100, NULL)`
437+
)
438+
await tx.execute(
439+
query`INSERT INTO workspace_file_version (id, file_id, size_bytes) VALUES ('version', 'file', 500)`
408440
)
409-
await tx.execute(query`INSERT INTO workspace_file_version VALUES ('version', 'file', 500)`)
410441
await prepared.applyDelta(100)
411442
})
412443
await database.transaction(async (tx) => {
@@ -441,11 +472,11 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
441472
check(
442473
'moves exact Project heads once across payers, including archived heads but excluding versions',
443474
async () => {
444-
await sql`INSERT INTO workspace_files VALUES
475+
await sql`INSERT INTO workspace_files (id, project_id, workspace_id, context, size_bytes, deleted_at) VALUES
445476
('active', 'project-a', NULL, 'project', 40, NULL),
446477
('archived', 'project-a', NULL, 'project', 60, now()),
447478
('other', 'project-b', NULL, 'project', 25, NULL)`
448-
await sql`INSERT INTO workspace_file_version VALUES ('version', 'active', 900)`
479+
await sql`INSERT INTO workspace_file_version (id, file_id, size_bytes) VALUES ('version', 'active', 900)`
449480
await sql`UPDATE organization SET storage_used_bytes = 125 WHERE id = 'organization-a'`
450481
await database.transaction((tx) =>
451482
changeProjectStoragePayersInTx(tx, [
@@ -484,12 +515,12 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
484515
check(
485516
'reconciliation adds Project heads once and refuses incomplete canonical size metadata',
486517
async () => {
487-
await sql`INSERT INTO workspace_files VALUES
518+
await sql`INSERT INTO workspace_files (id, project_id, workspace_id, context, size_bytes, deleted_at) VALUES
488519
('workspace-file', NULL, 'workspace-a', 'workspace', 20, NULL),
489520
('project-file', 'project-a', NULL, 'project', 40, NULL),
490521
('archived', 'project-a', NULL, 'project', 60, now()),
491522
('chat', NULL, 'workspace-a', 'mothership', 999, NULL)`
492-
await sql`INSERT INTO workspace_file_version VALUES ('version', 'project-file', 900)`
523+
await sql`INSERT INTO workspace_file_version (id, file_id, size_bytes) VALUES ('version', 'project-file', 900)`
493524
const store = createPostgresStorageReconciliationStore(sql)
494525
await store.reconcileWorkspaces(['workspace-a'])
495526
await store.reconcileOrganization('organization-a')
@@ -512,7 +543,7 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
512543
const write = database.transaction(async (tx) => {
513544
const prepared = await prepareProjectStorageMutationInTx(tx, context())
514545
await tx.execute(
515-
query`INSERT INTO workspace_files VALUES ('file', 'project-a', NULL, 'project', 40, NULL)`
546+
query`INSERT INTO workspace_files (id, project_id, workspace_id, context, size_bytes, deleted_at) VALUES ('file', 'project-a', NULL, 'project', 40, NULL)`
516547
)
517548
await prepared.applyDelta(40)
518549
changed.resolve()
@@ -549,7 +580,7 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
549580
const write = database.transaction(async (tx) => {
550581
const prepared = await prepareProjectStorageMutationInTx(tx, context())
551582
await tx.execute(
552-
query`INSERT INTO workspace_files VALUES ('file', 'project-a', NULL, 'project', 40, NULL)`
583+
query`INSERT INTO workspace_files (id, project_id, workspace_id, context, size_bytes, deleted_at) VALUES ('file', 'project-a', NULL, 'project', 40, NULL)`
553584
)
554585
await prepared.applyDelta(40)
555586
changed.resolve()

0 commit comments

Comments
 (0)