Skip to content

Commit 9edf892

Browse files
authored
docs(folders): correct four comments that claimed more than the code does (#6063)
A comment pass over this branch found four factual errors, all mine: - the admin route test justified its column-pinned assertion with reasoning copied from lifecycle.test.ts — that condition has no second isNull node, so the stated vacuity could not occur there - 'every clause mirrors one column of the partial unique index' was wrong: deletedAt is the index PREDICATE, and name has no clause at all - 'optimistic client reparents can write a cycle' is unsupported — wouldCreateFolderCycle has one caller and no write path bypasses it; the real origin is two concurrent reparents each passing the check - 'restoreFolder runs restoreChildren BEFORE un-archiving' holds only for the injected hook path; the in-transaction fallback runs after restoreFolderRows The rest trims comments that restated the code or duplicated a TSDoc block on the module under test, and pins three presence-only isNull assertions to the deletedAt column so they cannot be satisfied by an unrelated nullable filter — the same fragility that made an earlier assertion here vacuous.
1 parent 0f12799 commit 9edf892

6 files changed

Lines changed: 45 additions & 52 deletions

File tree

apps/sim/app/api/v1/admin/workspaces/[id]/folders/route.test.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@ describe('admin workspace folders GET', () => {
3939
resetDbChainMock()
4040
})
4141

42-
/**
43-
* Both the count and the page must exclude soft-deleted folders. Without the filter an operator
44-
* inspecting a workspace sees folders that live in Recently Deleted and an inflated total, and
45-
* this endpoint disagrees with every user-facing folder list — all of which filter `deletedAt`.
46-
*/
4742
it('excludes soft-deleted folders from both the count and the page', async () => {
4843
queueTableRows(schemaMock.workspace, [{ id: WORKSPACE_ID }])
4944
queueTableRows(schemaMock.folder, [{ total: 0 }])
@@ -55,8 +50,8 @@ describe('admin workspace folders GET', () => {
5550
const folderWheres = dbChainMockFns.where.mock.calls.slice(1).map(([where]) => where)
5651
expect(folderWheres.length).toBeGreaterThanOrEqual(2)
5752
for (const where of folderWheres) {
58-
// Asserted on the COLUMN: `resourceType`/`workspaceId` are eq nodes, so a bare
59-
// "some isNull exists" check could pass on an unrelated clause.
53+
// Pinned to the column so the assertion stays meaningful if another nullable filter
54+
// (e.g. a parent scope) is ever added to this condition.
6055
expect(
6156
flattenMockConditions(where).some(
6257
(node) => node.type === 'isNull' && node.column === schemaMock.folder.deletedAt

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,9 @@ export const GET = withRouteHandler(
4747
}
4848

4949
/**
50-
* Soft-deleted folders are excluded. Without this the count and the page both include rows
51-
* sitting in Recently Deleted, so an operator inspecting a workspace sees phantom folders
52-
* and an inflated total — and the two disagree with every user-facing folder list, all of
53-
* which filter on `deletedAt`.
50+
* Without the `deletedAt` filter the count and the page both include rows sitting in
51+
* Recently Deleted, so an operator sees phantom folders and an inflated total, and this
52+
* endpoint disagrees with every user-facing folder list.
5453
*/
5554
const activeWorkflowFolders = and(
5655
eq(folderTable.workspaceId, workspaceId),

apps/sim/lib/folders/lifecycle.test.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,13 +231,9 @@ describe('createFolder', () => {
231231
expect(dbChainMockFns.values).toHaveBeenCalledWith(expect.objectContaining({ sortOrder: -3 }))
232232
})
233233

234+
// Asserted on the WHERE clauses, not the resulting sortOrder: the mock returns whatever was
235+
// queued regardless of the filter, so a sortOrder assertion passes without either clause.
234236
it('ignores soft-deleted folders and resources when picking the new sortOrder', async () => {
235-
/**
236-
* `min - 1` means archived rows would ratchet the floor further negative on every delete and
237-
* never recover. Both minima must therefore see only rows a user can still see. Asserted on
238-
* the WHERE clauses because the mock returns whatever is queued regardless of the filter, so
239-
* an assertion on the resulting sortOrder alone would pass without either clause.
240-
*/
241237
setConfig({
242238
resourceType: 'workflow',
243239
countKey: 'workflows',
@@ -254,8 +250,8 @@ describe('createFolder', () => {
254250
.map(([where]) => where)
255251

256252
// Assert on the specific COLUMN, not merely that some isNull exists: for a root folder the
257-
// parent condition is itself `isNull(parentId)`, so a presence-only check passes with the
258-
// soft-delete filter deleted. That made the first version of this test vacuous.
253+
// parent condition is itself `isNull(parentId)`, so a presence-only check stays green with
254+
// the soft-delete filter deleted.
259255
expect(
260256
flattenMockConditions(folderWhere).some(
261257
(node) => node.type === 'isNull' && node.column === schemaMock.folder.deletedAt

apps/sim/lib/folders/lifecycle.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,9 @@ export async function nextFolderSortOrder(
129129
: isNull(folderTable.parentId)
130130

131131
/**
132-
* Soft-deleted rows are excluded from both minima. This function returns `min - 1` to put a
133-
* new folder at the top, so counting archived rows lets every delete ratchet the floor further
134-
* negative and never recover — an archived folder at -400 forces the next new folder to -401
135-
* forever. Only rows a user can actually see should influence the ordering. The Files path
136-
* (`workspace-file-folder-manager`) has always filtered this way.
132+
* Both minima exclude soft-deleted rows. This returns `min - 1` to put a new folder at the
133+
* top, so counting archived rows lets every delete ratchet the floor further negative and
134+
* never recover — an archived folder at -400 pins the next new folder at -401 forever.
137135
*/
138136
const folderMinPromise = tx
139137
.select({ minSortOrder: min(folderTable.sortOrder) })

apps/sim/lib/folders/naming.test.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,8 @@ function makeTx(siblingNames: string[]) {
2929
}
3030

3131
/**
32-
* The suffix shape is a cross-surface contract: the client's `nextUntitledFolderName` and
33-
* migration 0272's backfill both produce `"<name> (N)"` starting at (1). A server-side drift
34-
* either collides on `folder_workspace_resource_parent_name_active_unique` (23505 on a path the
35-
* user cannot retry) or renders a folder named differently depending on how it was created.
36-
* Nothing asserted this before — every caller mocks this module out.
32+
* Pins the `"<name> (N)"` shape the client and migration 0272 also produce (see `naming.ts`).
33+
* Every caller mocks this module out, so nothing else in the suite covers it.
3734
*/
3835
describe('deduplicateFolderName', () => {
3936
it('returns the requested name untouched when no sibling holds it', async () => {
@@ -43,8 +40,6 @@ describe('deduplicateFolderName', () => {
4340
})
4441

4542
it('starts the suffix at (1), not (2)', async () => {
46-
// A loop seeded at 2 — the shape of a bug already fixed twice in this feature — yields
47-
// "Reports (2)" here and silently diverges from the client and the migration.
4843
const { tx } = makeTx(['Reports'])
4944

5045
expect(await deduplicateFolderName(tx, 'ws-1', null, 'Reports', 'workflow')).toBe('Reports (1)')
@@ -73,9 +68,9 @@ describe('deduplicateFolderName', () => {
7368
})
7469

7570
/**
76-
* The sibling query defines the namespace the suffix is chosen within. Every clause below
77-
* mirrors one column of the partial unique index — dropping any of them counts the wrong rows
78-
* and either inflates the suffix or picks a name that is already taken.
71+
* The sibling query defines the namespace the suffix is chosen within, and must match the
72+
* scope of the partial unique index — workspace, resourceType, parent, active-only. Drop any
73+
* clause and it counts the wrong rows, inflating the suffix or picking a taken name.
7974
*/
8075
describe('sibling scoping', () => {
8176
it('scopes to workspace, resourceType, root parent, and active rows', async () => {
@@ -111,6 +106,7 @@ describe('deduplicateFolderName', () => {
111106

112107
await deduplicateFolderName(tx, 'ws-1', null, 'Reports', 'workflow')
113108

109+
// Two isNull nodes: the root parent scope and the soft-delete filter.
114110
expect(
115111
flattenMockConditions(selectCalls[0].where).filter((n) => n.type === 'isNull')
116112
).toHaveLength(2)

apps/sim/lib/folders/queries.test.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,9 @@ describe('folder queries', () => {
4343
})
4444

4545
/**
46-
* These are the id-keyed lookups. Every other query in the feature is already scoped by
47-
* workspace + resourceType through a list filter, but these accept a caller-supplied id — so
48-
* they are the one place a missing `resource_type` clause silently crosses resource trees,
49-
* filing a knowledge base under a table folder where no page will ever render it. Nothing
50-
* asserted this before: every caller mocks this module out, so deleting the clause left the
51-
* whole suite green.
46+
* The only lookup keyed on a caller-supplied folder id, so the `resourceType` clause is the
47+
* sole guard against crossing resource trees (rationale in `findActiveFolder`). Every caller
48+
* mocks this module out, so deleting that clause used to leave the whole suite green.
5249
*/
5350
describe('findActiveFolder', () => {
5451
it('scopes by id, workspace, resourceType, and active state', async () => {
@@ -63,7 +60,13 @@ describe('folder queries', () => {
6360
true
6461
)
6562
// Archived folders are not valid destinations — a row filed under one is unreachable.
66-
expect(hasMockCondition(where, (n) => n.type === 'isNull')).toBe(true)
63+
// Pinned to the column so the check cannot be satisfied by some other nullable filter.
64+
expect(
65+
hasMockCondition(
66+
where,
67+
(n) => n.type === 'isNull' && n.column === schemaMock.folder.deletedAt
68+
)
69+
).toBe(true)
6770
})
6871

6972
it('returns null when no row matches', async () => {
@@ -100,7 +103,8 @@ describe('folder queries', () => {
100103
})
101104

102105
it('terminates on a pre-existing cycle above the folder', async () => {
103-
// `visited` is what stops this looping forever; optimistic client reparents can write one.
106+
// `visited` is what stops this looping forever; concurrent reparents can each pass the
107+
// check and land a cycle.
104108
queueTableRows(schemaMock.folder, [{ parentId: 'b' }])
105109
queueTableRows(schemaMock.folder, [{ parentId: 'a' }])
106110

@@ -115,9 +119,9 @@ describe('folder queries', () => {
115119
})
116120

117121
/**
118-
* The `restoringFolderIds` short-circuit is load-bearing for cascade ordering: `restoreFolder`
119-
* runs its `restoreChildren` hook BEFORE un-archiving the folder rows, so a plain "is my folder
120-
* active?" check sees them still archived and dumps the entire subtree at the workspace root.
122+
* The `restoringFolderIds` short-circuit is load-bearing for cascade ordering: the
123+
* `config.restoreChildren` hook path runs before the un-archive transaction, so without the
124+
* set a plain "is my folder active?" check dumps the subtree at the workspace root.
121125
*/
122126
describe('resolveRestoredFolderId', () => {
123127
it('keeps the folder without querying when it is in the restoring set', async () => {
@@ -155,7 +159,12 @@ describe('folder queries', () => {
155159
const where = whereAt(0)
156160
expect(hasMockCondition(where, (n) => n.type === 'eq' && n.right === 'ws-1')).toBe(true)
157161
expect(hasMockCondition(where, (n) => n.type === 'eq' && n.right === 'table')).toBe(true)
158-
expect(hasMockCondition(where, (n) => n.type === 'isNull')).toBe(true)
162+
expect(
163+
hasMockCondition(
164+
where,
165+
(n) => n.type === 'isNull' && n.column === schemaMock.folder.deletedAt
166+
)
167+
).toBe(true)
159168
expect(hasMockCondition(where, (n) => n.type === 'isNotNull')).toBe(false)
160169
})
161170

@@ -165,16 +174,16 @@ describe('folder queries', () => {
165174
await listFoldersForWorkspace('ws-1', 'archived', 'workflow')
166175

167176
const where = whereAt(0)
168-
expect(hasMockCondition(where, (n) => n.type === 'isNotNull')).toBe(true)
177+
expect(
178+
hasMockCondition(
179+
where,
180+
(n) => n.type === 'isNotNull' && n.column === schemaMock.folder.deletedAt
181+
)
182+
).toBe(true)
169183
expect(hasMockCondition(where, (n) => n.type === 'isNull')).toBe(false)
170184
})
171185
})
172186

173-
/**
174-
* `requestJson` validates responses against the contract, so a route returning a raw row fails
175-
* client-side parse AFTER its write has already committed. This normalizer is the single point
176-
* that keeps every folder route emitting the same wire shape.
177-
*/
178187
describe('toFolderApi', () => {
179188
it('serializes timestamps to ISO strings and preserves a null deletedAt', () => {
180189
expect(toFolderApi(ROW)).toMatchObject({

0 commit comments

Comments
 (0)