json-rest-api 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +13 -0
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +9 -5
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +8 -8
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,9 +1,9 @@
1
- import { describe, it, before, after, beforeEach } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import { cleanTables } from './helpers/test-utils.js';
5
- import { createPositioningApi } from './fixtures/api-configs.js';
6
- import { PositioningPlugin } from '../plugins/core/rest-api-positioning-plugin.js';
1
+ import { describe, it, before, after, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import { cleanTables } from './helpers/test-utils.js'
5
+ import { createPositioningApi } from './fixtures/api-configs.js'
6
+ import { PositioningPlugin } from '../plugins/core/rest-api-positioning-plugin.js'
7
7
 
8
8
  // Create Knex instance for tests
9
9
  const knex = knexLib({
@@ -12,486 +12,486 @@ const knex = knexLib({
12
12
  filename: ':memory:'
13
13
  },
14
14
  useNullAsDefault: true
15
- });
15
+ })
16
16
 
17
17
  // API instance that persists across tests
18
- let api;
18
+ let api
19
19
 
20
20
  describe('Extended Positioning Plugin Tests', { timeout: 30000 }, () => {
21
21
  before(async () => {
22
22
  // Initialize API once
23
- api = await createPositioningApi(knex);
24
-
23
+ api = await createPositioningApi(knex)
24
+
25
25
  // Install positioning plugin with default configuration
26
26
  await api.use(PositioningPlugin, {
27
27
  field: 'position',
28
28
  filters: ['category'],
29
29
  defaultPosition: 'last',
30
30
  autoIndex: true
31
- });
32
- });
33
-
31
+ })
32
+ })
33
+
34
34
  after(async () => {
35
- await knex.destroy();
36
- });
37
-
35
+ await knex.destroy()
36
+ })
37
+
38
38
  beforeEach(async () => {
39
39
  await cleanTables(knex, [
40
40
  'positioning_categories',
41
41
  'positioning_tasks',
42
42
  'positioning_projects',
43
43
  'positioning_items'
44
- ]);
45
- });
44
+ ])
45
+ })
46
46
 
47
47
  describe('Edge Cases for Positioning', () => {
48
48
  it('should handle positioning multiple items before the same target in sequence', async () => {
49
- const category = await api.resources.categories.post({ name: 'Test Category' });
50
-
49
+ const category = await api.resources.categories.post({ name: 'Test Category' })
50
+
51
51
  // Create initial task
52
52
  const task1 = await api.resources.tasks.post({
53
53
  title: 'Task 1',
54
54
  category: category.id
55
- });
56
-
55
+ })
56
+
57
57
  // Position multiple tasks before task1
58
58
  const taskA = await api.resources.tasks.post({
59
59
  title: 'Task A',
60
60
  category: category.id,
61
61
  beforeId: task1.id
62
- });
63
-
62
+ })
63
+
64
64
  const taskB = await api.resources.tasks.post({
65
65
  title: 'Task B',
66
66
  category: category.id,
67
67
  beforeId: task1.id
68
- });
69
-
68
+ })
69
+
70
70
  const taskC = await api.resources.tasks.post({
71
71
  title: 'Task C',
72
72
  category: category.id,
73
73
  beforeId: task1.id
74
- });
75
-
74
+ })
75
+
76
76
  // Query and verify order
77
77
  const { data: tasks } = await api.resources.tasks.query({
78
78
  queryParams: {
79
79
  filters: { category: category.id },
80
80
  sort: ['position']
81
81
  }
82
- });
83
-
82
+ })
83
+
84
84
  // Should be: A, B, C, 1 (in creation order when positioned before same target)
85
- assert.equal(tasks.length, 4);
86
- assert.equal(tasks[0].title, 'Task A');
87
- assert.equal(tasks[1].title, 'Task B');
88
- assert.equal(tasks[2].title, 'Task C');
89
- assert.equal(tasks[3].title, 'Task 1');
90
-
85
+ assert.equal(tasks.length, 4)
86
+ assert.equal(tasks[0].title, 'Task A')
87
+ assert.equal(tasks[1].title, 'Task B')
88
+ assert.equal(tasks[2].title, 'Task C')
89
+ assert.equal(tasks[3].title, 'Task 1')
90
+
91
91
  // Verify positions are unique
92
- const positions = tasks.map(t => t.position);
93
- const uniquePositions = [...new Set(positions)];
94
- assert.equal(uniquePositions.length, 4, 'All positions should be unique');
95
- });
92
+ const positions = tasks.map(t => t.position)
93
+ const uniquePositions = [...new Set(positions)]
94
+ assert.equal(uniquePositions.length, 4, 'All positions should be unique')
95
+ })
96
96
 
97
97
  it('should handle positioning between very close positions', async () => {
98
- const category = await api.resources.categories.post({ name: 'Test Category' });
99
-
98
+ const category = await api.resources.categories.post({ name: 'Test Category' })
99
+
100
100
  // Create initial tasks
101
101
  const task1 = await api.resources.tasks.post({
102
102
  title: 'Task 1',
103
103
  category: category.id
104
- });
105
-
104
+ })
105
+
106
106
  const task2 = await api.resources.tasks.post({
107
107
  title: 'Task 2',
108
108
  category: category.id
109
- });
110
-
109
+ })
110
+
111
111
  // Insert many tasks between 1 and 2
112
- const insertedTasks = [];
112
+ const insertedTasks = []
113
113
  for (let i = 0; i < 10; i++) {
114
114
  const task = await api.resources.tasks.post({
115
115
  title: `Inserted Task ${i}`,
116
116
  category: category.id,
117
117
  beforeId: task2.id
118
- });
119
- insertedTasks.push(task);
118
+ })
119
+ insertedTasks.push(task)
120
120
  }
121
-
121
+
122
122
  // Query and verify all are properly ordered
123
123
  const { data: tasks } = await api.resources.tasks.query({
124
124
  queryParams: {
125
125
  filters: { category: category.id },
126
126
  sort: ['position']
127
127
  }
128
- });
129
-
130
- assert.equal(tasks.length, 12);
131
-
128
+ })
129
+
130
+ assert.equal(tasks.length, 12)
131
+
132
132
  // Verify positions are in correct order
133
133
  for (let i = 1; i < tasks.length; i++) {
134
- assert(tasks[i-1].position < tasks[i].position,
135
- `Task ${tasks[i-1].title} should come before ${tasks[i].title}`);
134
+ assert(tasks[i - 1].position < tasks[i].position,
135
+ `Task ${tasks[i - 1].title} should come before ${tasks[i].title}`)
136
136
  }
137
- });
137
+ })
138
138
 
139
139
  it('should handle beforeId pointing to non-existent item in same category', async () => {
140
- const category = await api.resources.categories.post({ name: 'Test Category' });
141
-
140
+ const category = await api.resources.categories.post({ name: 'Test Category' })
141
+
142
142
  const task1 = await api.resources.tasks.post({
143
143
  title: 'Task 1',
144
144
  category: category.id
145
- });
146
-
145
+ })
146
+
147
147
  // Create task with non-existent beforeId
148
148
  const task2 = await api.resources.tasks.post({
149
149
  title: 'Task 2',
150
150
  category: category.id,
151
151
  beforeId: '99999'
152
- });
153
-
152
+ })
153
+
154
154
  // Should position at end since beforeId doesn't exist
155
155
  const { data: tasks } = await api.resources.tasks.query({
156
156
  queryParams: {
157
157
  filters: { category: category.id },
158
158
  sort: ['position']
159
159
  }
160
- });
161
-
162
- assert.equal(tasks.length, 2);
163
- assert.equal(tasks[0].title, 'Task 1');
164
- assert.equal(tasks[1].title, 'Task 2');
165
- });
160
+ })
161
+
162
+ assert.equal(tasks.length, 2)
163
+ assert.equal(tasks[0].title, 'Task 1')
164
+ assert.equal(tasks[1].title, 'Task 2')
165
+ })
166
166
 
167
167
  it('should handle beforeId from different category', async () => {
168
- const category1 = await api.resources.categories.post({ name: 'Category 1' });
169
- const category2 = await api.resources.categories.post({ name: 'Category 2' });
170
-
168
+ const category1 = await api.resources.categories.post({ name: 'Category 1' })
169
+ const category2 = await api.resources.categories.post({ name: 'Category 2' })
170
+
171
171
  const task1 = await api.resources.tasks.post({
172
172
  title: 'Task 1',
173
173
  category: category1.id
174
- });
175
-
174
+ })
175
+
176
176
  const task2 = await api.resources.tasks.post({
177
177
  title: 'Task 2',
178
178
  category: category2.id,
179
179
  beforeId: task1.id // Task from different category
180
- });
181
-
180
+ })
181
+
182
182
  // Task 2 should be positioned normally in its category (ignoring beforeId from other category)
183
183
  const { data: cat2Tasks } = await api.resources.tasks.query({
184
184
  queryParams: {
185
185
  filters: { category: category2.id },
186
186
  sort: ['position']
187
187
  }
188
- });
189
-
190
- assert.equal(cat2Tasks.length, 1);
191
- assert.equal(cat2Tasks[0].title, 'Task 2');
192
- assert(cat2Tasks[0].position); // Should have a position
193
- });
194
- });
188
+ })
189
+
190
+ assert.equal(cat2Tasks.length, 1)
191
+ assert.equal(cat2Tasks[0].title, 'Task 2')
192
+ assert(cat2Tasks[0].position) // Should have a position
193
+ })
194
+ })
195
195
 
196
196
  describe('Complex Update Scenarios', () => {
197
197
  it('should handle moving items multiple times', async () => {
198
- const category = await api.resources.categories.post({ name: 'Test Category' });
199
-
198
+ const category = await api.resources.categories.post({ name: 'Test Category' })
199
+
200
200
  // Create initial tasks
201
- const tasks = [];
201
+ const tasks = []
202
202
  for (let i = 1; i <= 5; i++) {
203
203
  const task = await api.resources.tasks.post({
204
204
  title: `Task ${i}`,
205
205
  category: category.id
206
- });
207
- tasks.push(task);
206
+ })
207
+ tasks.push(task)
208
208
  }
209
-
209
+
210
210
  // Move task 5 before task 3
211
211
  await api.resources.tasks.patch({
212
212
  id: tasks[4].id,
213
213
  beforeId: tasks[2].id
214
- });
215
-
214
+ })
215
+
216
216
  // Move task 1 to the end
217
217
  await api.resources.tasks.patch({
218
218
  id: tasks[0].id,
219
219
  beforeId: null
220
- });
221
-
220
+ })
221
+
222
222
  // Move task 3 to the beginning
223
223
  await api.resources.tasks.patch({
224
224
  id: tasks[2].id,
225
225
  beforeId: 'FIRST'
226
- });
227
-
226
+ })
227
+
228
228
  // Query final order
229
229
  const { data: finalTasks } = await api.resources.tasks.query({
230
230
  queryParams: {
231
231
  filters: { category: category.id },
232
232
  sort: ['position']
233
233
  }
234
- });
235
-
234
+ })
235
+
236
236
  // Expected order: 3, 2, 5, 4, 1
237
- assert.equal(finalTasks[0].title, 'Task 3');
238
- assert.equal(finalTasks[1].title, 'Task 2');
239
- assert.equal(finalTasks[2].title, 'Task 5');
240
- assert.equal(finalTasks[3].title, 'Task 4');
241
- assert.equal(finalTasks[4].title, 'Task 1');
242
- });
237
+ assert.equal(finalTasks[0].title, 'Task 3')
238
+ assert.equal(finalTasks[1].title, 'Task 2')
239
+ assert.equal(finalTasks[2].title, 'Task 5')
240
+ assert.equal(finalTasks[3].title, 'Task 4')
241
+ assert.equal(finalTasks[4].title, 'Task 1')
242
+ })
243
243
 
244
244
  it('should handle category changes with positioning', async () => {
245
- const category1 = await api.resources.categories.post({ name: 'Category 1' });
246
- const category2 = await api.resources.categories.post({ name: 'Category 2' });
247
-
245
+ const category1 = await api.resources.categories.post({ name: 'Category 1' })
246
+ const category2 = await api.resources.categories.post({ name: 'Category 2' })
247
+
248
248
  // Create tasks in category 1
249
249
  const task1 = await api.resources.tasks.post({
250
250
  title: 'Task 1',
251
251
  category: category1.id
252
- });
253
-
252
+ })
253
+
254
254
  const task2 = await api.resources.tasks.post({
255
255
  title: 'Task 2',
256
256
  category: category1.id
257
- });
258
-
257
+ })
258
+
259
259
  // Create tasks in category 2
260
260
  const task3 = await api.resources.tasks.post({
261
261
  title: 'Task 3',
262
262
  category: category2.id
263
- });
264
-
263
+ })
264
+
265
265
  // Move task 1 to category 2, positioning it before task 3
266
266
  await api.resources.tasks.patch({
267
267
  id: task1.id,
268
268
  category: category2.id,
269
269
  beforeId: task3.id
270
- });
271
-
270
+ })
271
+
272
272
  // Verify category 1 only has task 2
273
273
  const { data: cat1Tasks } = await api.resources.tasks.query({
274
274
  queryParams: {
275
275
  filters: { category: category1.id },
276
276
  sort: ['position']
277
277
  }
278
- });
279
-
280
- assert.equal(cat1Tasks.length, 1);
281
- assert.equal(cat1Tasks[0].title, 'Task 2');
282
-
278
+ })
279
+
280
+ assert.equal(cat1Tasks.length, 1)
281
+ assert.equal(cat1Tasks[0].title, 'Task 2')
282
+
283
283
  // Verify category 2 has tasks in correct order
284
284
  const { data: cat2Tasks } = await api.resources.tasks.query({
285
285
  queryParams: {
286
286
  filters: { category: category2.id },
287
287
  sort: ['position']
288
288
  }
289
- });
290
-
291
- assert.equal(cat2Tasks.length, 2);
292
- assert.equal(cat2Tasks[0].title, 'Task 1');
293
- assert.equal(cat2Tasks[1].title, 'Task 3');
294
- });
289
+ })
290
+
291
+ assert.equal(cat2Tasks.length, 2)
292
+ assert.equal(cat2Tasks[0].title, 'Task 1')
293
+ assert.equal(cat2Tasks[1].title, 'Task 3')
294
+ })
295
295
 
296
296
  it('should maintain position when updating other fields', async () => {
297
- const category = await api.resources.categories.post({ name: 'Test Category' });
298
-
297
+ const category = await api.resources.categories.post({ name: 'Test Category' })
298
+
299
299
  // Create tasks
300
- const tasks = [];
300
+ const tasks = []
301
301
  for (let i = 1; i <= 3; i++) {
302
302
  const task = await api.resources.tasks.post({
303
303
  title: `Task ${i}`,
304
304
  category: category.id
305
- });
306
- tasks.push(task);
305
+ })
306
+ tasks.push(task)
307
307
  }
308
-
308
+
309
309
  // Update title without beforeId
310
- const originalPosition = tasks[1].position;
310
+ const originalPosition = tasks[1].position
311
311
  await api.resources.tasks.patch({
312
312
  id: tasks[1].id,
313
313
  title: 'Updated Task 2'
314
- });
315
-
314
+ })
315
+
316
316
  // Get updated task
317
- const updatedTask = await api.resources.tasks.get({ id: tasks[1].id });
318
-
319
- assert.equal(updatedTask.title, 'Updated Task 2');
320
- assert.equal(updatedTask.position, originalPosition, 'Position should not change');
321
-
317
+ const updatedTask = await api.resources.tasks.get({ id: tasks[1].id })
318
+
319
+ assert.equal(updatedTask.title, 'Updated Task 2')
320
+ assert.equal(updatedTask.position, originalPosition, 'Position should not change')
321
+
322
322
  // Verify order is maintained
323
323
  const { data: allTasks } = await api.resources.tasks.query({
324
324
  queryParams: {
325
325
  filters: { category: category.id },
326
326
  sort: ['position']
327
327
  }
328
- });
329
-
330
- assert.equal(allTasks[0].title, 'Task 1');
331
- assert.equal(allTasks[1].title, 'Updated Task 2');
332
- assert.equal(allTasks[2].title, 'Task 3');
333
- });
334
- });
328
+ })
329
+
330
+ assert.equal(allTasks[0].title, 'Task 1')
331
+ assert.equal(allTasks[1].title, 'Updated Task 2')
332
+ assert.equal(allTasks[2].title, 'Task 3')
333
+ })
334
+ })
335
335
 
336
336
  describe('Bulk Operations', () => {
337
337
  it('should handle bulk creation with positioning', async () => {
338
- const category = await api.resources.categories.post({ name: 'Test Category' });
339
-
338
+ const category = await api.resources.categories.post({ name: 'Test Category' })
339
+
340
340
  // Create initial task
341
341
  const task1 = await api.resources.tasks.post({
342
342
  title: 'Task 1',
343
343
  category: category.id
344
- });
345
-
344
+ })
345
+
346
346
  // Create multiple tasks in parallel (simulating bulk creation)
347
- const promises = [];
347
+ const promises = []
348
348
  for (let i = 2; i <= 10; i++) {
349
349
  promises.push(api.resources.tasks.post({
350
350
  title: `Task ${i}`,
351
351
  category: category.id
352
- }));
352
+ }))
353
353
  }
354
-
355
- await Promise.all(promises);
356
-
354
+
355
+ await Promise.all(promises)
356
+
357
357
  // Query all tasks
358
358
  const { data: tasks } = await api.resources.tasks.query({
359
359
  queryParams: {
360
360
  filters: { category: category.id },
361
361
  sort: ['position']
362
362
  }
363
- });
364
-
365
- assert.equal(tasks.length, 10);
366
-
363
+ })
364
+
365
+ assert.equal(tasks.length, 10)
366
+
367
367
  // Verify all have unique positions
368
- const positions = tasks.map(t => t.position);
369
- const uniquePositions = [...new Set(positions)];
370
- assert.equal(uniquePositions.length, 10, 'All positions should be unique');
371
-
368
+ const positions = tasks.map(t => t.position)
369
+ const uniquePositions = [...new Set(positions)]
370
+ assert.equal(uniquePositions.length, 10, 'All positions should be unique')
371
+
372
372
  // Verify they're in order
373
373
  for (let i = 1; i < tasks.length; i++) {
374
- assert(tasks[i-1].position < tasks[i].position);
374
+ assert(tasks[i - 1].position < tasks[i].position)
375
375
  }
376
- });
376
+ })
377
377
 
378
378
  it('should handle bulk positioning before same target', async () => {
379
- const category = await api.resources.categories.post({ name: 'Test Category' });
380
-
379
+ const category = await api.resources.categories.post({ name: 'Test Category' })
380
+
381
381
  // Create initial tasks
382
382
  const task1 = await api.resources.tasks.post({
383
383
  title: 'Task 1',
384
384
  category: category.id
385
- });
386
-
385
+ })
386
+
387
387
  const task2 = await api.resources.tasks.post({
388
388
  title: 'Task 2',
389
389
  category: category.id
390
- });
391
-
390
+ })
391
+
392
392
  // Create multiple tasks before task2 in parallel
393
- const promises = [];
393
+ const promises = []
394
394
  for (let i = 1; i <= 5; i++) {
395
395
  promises.push(api.resources.tasks.post({
396
396
  title: `Inserted Task ${i}`,
397
397
  category: category.id,
398
398
  beforeId: task2.id
399
- }));
399
+ }))
400
400
  }
401
-
402
- await Promise.all(promises);
403
-
401
+
402
+ await Promise.all(promises)
403
+
404
404
  // Query all tasks
405
405
  const { data: tasks } = await api.resources.tasks.query({
406
406
  queryParams: {
407
407
  filters: { category: category.id },
408
408
  sort: ['position']
409
409
  }
410
- });
411
-
412
- assert.equal(tasks.length, 7);
413
-
410
+ })
411
+
412
+ assert.equal(tasks.length, 7)
413
+
414
414
  // Task 1 should still be first
415
- assert.equal(tasks[0].title, 'Task 1');
416
-
415
+ assert.equal(tasks[0].title, 'Task 1')
416
+
417
417
  // All inserted tasks should come before Task 2
418
- const task2Index = tasks.findIndex(t => t.title === 'Task 2');
419
- assert.equal(task2Index, 6, 'Task 2 should be last');
420
-
418
+ const task2Index = tasks.findIndex(t => t.title === 'Task 2')
419
+ assert.equal(task2Index, 6, 'Task 2 should be last')
420
+
421
421
  // All positions should be unique
422
- const positions = tasks.map(t => t.position);
423
- const uniquePositions = [...new Set(positions)];
424
- assert.equal(uniquePositions.length, 7, 'All positions should be unique');
425
- });
426
- });
422
+ const positions = tasks.map(t => t.position)
423
+ const uniquePositions = [...new Set(positions)]
424
+ assert.equal(uniquePositions.length, 7, 'All positions should be unique')
425
+ })
426
+ })
427
427
 
428
428
  describe('Query and Filtering', () => {
429
429
  it('should sort correctly with mixed position values', async () => {
430
- const category = await api.resources.categories.post({ name: 'Test Category' });
431
-
430
+ const category = await api.resources.categories.post({ name: 'Test Category' })
431
+
432
432
  // Create tasks in random order
433
433
  const taskC = await api.resources.tasks.post({
434
434
  title: 'Task C',
435
435
  category: category.id
436
- });
437
-
436
+ })
437
+
438
438
  const taskA = await api.resources.tasks.post({
439
439
  title: 'Task A',
440
440
  category: category.id,
441
441
  beforeId: taskC.id
442
- });
443
-
442
+ })
443
+
444
444
  const taskE = await api.resources.tasks.post({
445
445
  title: 'Task E',
446
446
  category: category.id
447
- });
448
-
447
+ })
448
+
449
449
  const taskB = await api.resources.tasks.post({
450
450
  title: 'Task B',
451
451
  category: category.id,
452
452
  beforeId: taskC.id
453
- });
454
-
453
+ })
454
+
455
455
  const taskD = await api.resources.tasks.post({
456
456
  title: 'Task D',
457
457
  category: category.id,
458
458
  beforeId: taskE.id
459
- });
460
-
459
+ })
460
+
461
461
  // Query with ascending sort
462
462
  const { data: ascTasks } = await api.resources.tasks.query({
463
463
  queryParams: {
464
464
  filters: { category: category.id },
465
465
  sort: ['position']
466
466
  }
467
- });
468
-
467
+ })
468
+
469
469
  // Should be alphabetical: A, B, C, D, E
470
- assert.equal(ascTasks.map(t => t.title).join(','), 'Task A,Task B,Task C,Task D,Task E');
471
-
470
+ assert.equal(ascTasks.map(t => t.title).join(','), 'Task A,Task B,Task C,Task D,Task E')
471
+
472
472
  // Query with descending sort
473
473
  const { data: descTasks } = await api.resources.tasks.query({
474
474
  queryParams: {
475
475
  filters: { category: category.id },
476
476
  sort: ['-position']
477
477
  }
478
- });
479
-
478
+ })
479
+
480
480
  // Should be reverse: E, D, C, B, A
481
- assert.equal(descTasks.map(t => t.title).join(','), 'Task E,Task D,Task C,Task B,Task A');
482
- });
481
+ assert.equal(descTasks.map(t => t.title).join(','), 'Task E,Task D,Task C,Task B,Task A')
482
+ })
483
483
 
484
484
  it('should handle pagination with position sorting', async () => {
485
- const category = await api.resources.categories.post({ name: 'Test Category' });
486
-
485
+ const category = await api.resources.categories.post({ name: 'Test Category' })
486
+
487
487
  // Create 20 tasks
488
488
  for (let i = 1; i <= 20; i++) {
489
489
  await api.resources.tasks.post({
490
490
  title: `Task ${i.toString().padStart(2, '0')}`,
491
491
  category: category.id
492
- });
492
+ })
493
493
  }
494
-
494
+
495
495
  // Query first page
496
496
  const { data: page1 } = await api.resources.tasks.query({
497
497
  queryParams: {
@@ -499,12 +499,12 @@ describe('Extended Positioning Plugin Tests', { timeout: 30000 }, () => {
499
499
  sort: ['position'],
500
500
  page: { size: 5, number: 1 }
501
501
  }
502
- });
503
-
504
- assert.equal(page1.length, 5);
505
- assert.equal(page1[0].title, 'Task 01');
506
- assert.equal(page1[4].title, 'Task 05');
507
-
502
+ })
503
+
504
+ assert.equal(page1.length, 5)
505
+ assert.equal(page1[0].title, 'Task 01')
506
+ assert.equal(page1[4].title, 'Task 05')
507
+
508
508
  // Query second page
509
509
  const { data: page2 } = await api.resources.tasks.query({
510
510
  queryParams: {
@@ -512,80 +512,80 @@ describe('Extended Positioning Plugin Tests', { timeout: 30000 }, () => {
512
512
  sort: ['position'],
513
513
  page: { size: 5, number: 2 }
514
514
  }
515
- });
516
-
517
- assert.equal(page2.length, 5);
518
- assert.equal(page2[0].title, 'Task 06');
519
- assert.equal(page2[4].title, 'Task 10');
520
-
515
+ })
516
+
517
+ assert.equal(page2.length, 5)
518
+ assert.equal(page2[0].title, 'Task 06')
519
+ assert.equal(page2[4].title, 'Task 10')
520
+
521
521
  // Verify no overlap and correct ordering
522
- assert(page1[4].position < page2[0].position);
523
- });
522
+ assert(page1[4].position < page2[0].position)
523
+ })
524
524
 
525
525
  it('should combine position sort with other sorts', async () => {
526
- const category = await api.resources.categories.post({ name: 'Test Category' });
527
-
526
+ const category = await api.resources.categories.post({ name: 'Test Category' })
527
+
528
528
  // Create tasks with same titles
529
- const positions = [];
529
+ const positions = []
530
530
  for (let i = 0; i < 3; i++) {
531
531
  const task = await api.resources.tasks.post({
532
532
  title: 'Task A',
533
533
  category: category.id
534
- });
535
- positions.push(task);
534
+ })
535
+ positions.push(task)
536
536
  }
537
-
537
+
538
538
  for (let i = 0; i < 3; i++) {
539
539
  const task = await api.resources.tasks.post({
540
540
  title: 'Task B',
541
541
  category: category.id
542
- });
543
- positions.push(task);
542
+ })
543
+ positions.push(task)
544
544
  }
545
-
545
+
546
546
  // Sort by title then position
547
547
  const { data: tasks } = await api.resources.tasks.query({
548
548
  queryParams: {
549
549
  filters: { category: category.id },
550
550
  sort: ['title', 'position']
551
551
  }
552
- });
553
-
552
+ })
553
+
554
554
  // First 3 should be Task A, last 3 should be Task B
555
- assert.equal(tasks.slice(0, 3).every(t => t.title === 'Task A'), true);
556
- assert.equal(tasks.slice(3, 6).every(t => t.title === 'Task B'), true);
557
-
555
+ assert.equal(tasks.slice(0, 3).every(t => t.title === 'Task A'), true)
556
+ assert.equal(tasks.slice(3, 6).every(t => t.title === 'Task B'), true)
557
+
558
558
  // Within each group, should be sorted by position
559
559
  for (let i = 1; i < 3; i++) {
560
- assert(tasks[i-1].position < tasks[i].position);
561
- assert(tasks[i+2].position < tasks[i+3].position);
560
+ assert(tasks[i - 1].position < tasks[i].position)
561
+ assert(tasks[i + 2].position < tasks[i + 3].position)
562
562
  }
563
- });
564
- });
563
+ })
564
+ })
565
565
 
566
566
  describe('Special Characters and Extreme Values', () => {
567
567
  it('should handle special position values correctly', async () => {
568
- const category = await api.resources.categories.post({ name: 'Test Category' });
569
-
568
+ const category = await api.resources.categories.post({ name: 'Test Category' })
569
+
570
570
  // Create many tasks to test various position values
571
- const tasks = [];
571
+ const tasks = []
572
572
  for (let i = 0; i < 50; i++) {
573
573
  const task = await api.resources.tasks.post({
574
574
  title: `Task ${i}`,
575
575
  category: category.id
576
- });
577
- tasks.push(task);
576
+ })
577
+ tasks.push(task)
578
578
  }
579
-
579
+
580
580
  // Insert tasks between existing ones
581
581
  for (let i = 0; i < 10; i++) {
582
582
  await api.resources.tasks.post({
583
583
  title: `Inserted ${i}`,
584
584
  category: category.id,
585
585
  beforeId: tasks[i * 5].id
586
- });
586
+ })
587
587
  }
588
-
588
+
589
589
  // Query all and verify ordering
590
590
  const { data: allTasks } = await api.resources.tasks.query({
591
591
  queryParams: {
@@ -593,241 +593,241 @@ describe('Extended Positioning Plugin Tests', { timeout: 30000 }, () => {
593
593
  sort: ['position'],
594
594
  page: { size: 100 }
595
595
  }
596
- });
597
-
598
- assert.equal(allTasks.length, 60);
599
-
596
+ })
597
+
598
+ assert.equal(allTasks.length, 60)
599
+
600
600
  // Verify all positions are valid strings
601
601
  for (const task of allTasks) {
602
- assert.equal(typeof task.position, 'string');
603
- assert(task.position.length > 0);
602
+ assert.equal(typeof task.position, 'string')
603
+ assert(task.position.length > 0)
604
604
  // Fractional indexing uses alphanumeric characters
605
- assert.match(task.position, /^[a-zA-Z0-9]+$/);
605
+ assert.match(task.position, /^[a-zA-Z0-9]+$/)
606
606
  }
607
-
607
+
608
608
  // Verify ordering
609
609
  for (let i = 1; i < allTasks.length; i++) {
610
- assert(allTasks[i-1].position < allTasks[i].position);
610
+ assert(allTasks[i - 1].position < allTasks[i].position)
611
611
  }
612
- });
612
+ })
613
613
 
614
614
  it('should handle rapid position updates', async () => {
615
- const category = await api.resources.categories.post({ name: 'Test Category' });
616
-
615
+ const category = await api.resources.categories.post({ name: 'Test Category' })
616
+
617
617
  // Create initial tasks
618
- const tasks = [];
618
+ const tasks = []
619
619
  for (let i = 0; i < 10; i++) {
620
620
  const task = await api.resources.tasks.post({
621
621
  title: `Task ${i}`,
622
622
  category: category.id
623
- });
624
- tasks.push(task);
623
+ })
624
+ tasks.push(task)
625
625
  }
626
-
626
+
627
627
  // Rapidly move last task to different positions
628
- const lastTask = tasks[9];
629
-
628
+ const lastTask = tasks[9]
629
+
630
630
  // Move to beginning
631
631
  await api.resources.tasks.patch({
632
632
  id: lastTask.id,
633
633
  beforeId: tasks[0].id
634
- });
635
-
634
+ })
635
+
636
636
  // Move to middle
637
637
  await api.resources.tasks.patch({
638
638
  id: lastTask.id,
639
639
  beforeId: tasks[5].id
640
- });
641
-
640
+ })
641
+
642
642
  // Move to near end
643
643
  await api.resources.tasks.patch({
644
644
  id: lastTask.id,
645
645
  beforeId: tasks[8].id
646
- });
647
-
646
+ })
647
+
648
648
  // Move back to beginning
649
649
  await api.resources.tasks.patch({
650
650
  id: lastTask.id,
651
651
  beforeId: 'FIRST'
652
- });
653
-
652
+ })
653
+
654
654
  // Verify final state
655
655
  const { data: finalTasks } = await api.resources.tasks.query({
656
656
  queryParams: {
657
657
  filters: { category: category.id },
658
658
  sort: ['position']
659
659
  }
660
- });
661
-
662
- assert.equal(finalTasks[0].title, 'Task 9');
663
-
660
+ })
661
+
662
+ assert.equal(finalTasks[0].title, 'Task 9')
663
+
664
664
  // Verify all positions are still unique and ordered
665
- const positions = finalTasks.map(t => t.position);
666
- const uniquePositions = [...new Set(positions)];
667
- assert.equal(uniquePositions.length, 10);
668
-
665
+ const positions = finalTasks.map(t => t.position)
666
+ const uniquePositions = [...new Set(positions)]
667
+ assert.equal(uniquePositions.length, 10)
668
+
669
669
  for (let i = 1; i < finalTasks.length; i++) {
670
- assert(finalTasks[i-1].position < finalTasks[i].position);
670
+ assert(finalTasks[i - 1].position < finalTasks[i].position)
671
671
  }
672
- });
673
- });
672
+ })
673
+ })
674
674
 
675
675
  describe('Null and Empty Filter Values', () => {
676
676
  it('should handle tasks with null category separately', async () => {
677
677
  // Create tasks with categories
678
- const category = await api.resources.categories.post({ name: 'Test Category' });
679
-
678
+ const category = await api.resources.categories.post({ name: 'Test Category' })
679
+
680
680
  const taskWithCat1 = await api.resources.tasks.post({
681
681
  title: 'Task with Category 1',
682
682
  category: category.id
683
- });
684
-
683
+ })
684
+
685
685
  const taskWithCat2 = await api.resources.tasks.post({
686
686
  title: 'Task with Category 2',
687
687
  category: category.id
688
- });
689
-
688
+ })
689
+
690
690
  // Create tasks without category - don't send category_id at all for null
691
691
  const taskNoCat1 = await api.resources.tasks.post({
692
692
  title: 'Task without Category 1'
693
- });
694
-
693
+ })
694
+
695
695
  const taskNoCat2 = await api.resources.tasks.post({
696
696
  title: 'Task without Category 2'
697
- });
698
-
697
+ })
698
+
699
699
  const taskNoCat3 = await api.resources.tasks.post({
700
700
  title: 'Task without Category 3',
701
701
  beforeId: taskNoCat1.id
702
- });
703
-
702
+ })
703
+
704
704
  // Query tasks with category
705
705
  const { data: tasksWithCat } = await api.resources.tasks.query({
706
706
  queryParams: {
707
707
  filters: { category: category.id },
708
708
  sort: ['position']
709
709
  }
710
- });
711
-
712
- assert.equal(tasksWithCat.length, 2);
713
- assert(tasksWithCat[0].position < tasksWithCat[1].position);
714
-
710
+ })
711
+
712
+ assert.equal(tasksWithCat.length, 2)
713
+ assert(tasksWithCat[0].position < tasksWithCat[1].position)
714
+
715
715
  // Query tasks without category
716
716
  const { data: tasksNoCat } = await api.resources.tasks.query({
717
717
  queryParams: {
718
718
  filters: { category: null },
719
719
  sort: ['position']
720
720
  }
721
- });
722
-
721
+ })
722
+
723
723
  // Debug: Check what we actually have
724
724
  const { data: allTasks } = await api.resources.tasks.query({
725
725
  queryParams: {
726
726
  sort: ['position']
727
727
  }
728
- });
729
- console.log('All tasks:', allTasks.map(t => ({ title: t.title, category: t.category_id })));
730
- console.log('Tasks with null category:', tasksNoCat.length);
731
-
728
+ })
729
+ console.log('All tasks:', allTasks.map(t => ({ title: t.title, category: t.category_id })))
730
+ console.log('Tasks with null category:', tasksNoCat.length)
731
+
732
732
  // Try querying without filter
733
733
  const { data: tasksNoFilter } = await api.resources.tasks.query({
734
734
  queryParams: {
735
735
  sort: ['position']
736
736
  }
737
- });
738
- const actualNullCategoryTasks = tasksNoFilter.filter(t => t.category_id === null || t.category_id === undefined);
739
- console.log('Actual tasks without category:', actualNullCategoryTasks.length);
740
-
741
- assert.equal(tasksNoCat.length, 3);
742
- assert.equal(tasksNoCat[0].title, 'Task without Category 3'); // Positioned before 1
743
- assert.equal(tasksNoCat[1].title, 'Task without Category 1');
744
- assert.equal(tasksNoCat[2].title, 'Task without Category 2');
745
- });
746
- });
737
+ })
738
+ const actualNullCategoryTasks = tasksNoFilter.filter(t => t.category_id === null || t.category_id === undefined)
739
+ console.log('Actual tasks without category:', actualNullCategoryTasks.length)
740
+
741
+ assert.equal(tasksNoCat.length, 3)
742
+ assert.equal(tasksNoCat[0].title, 'Task without Category 3') // Positioned before 1
743
+ assert.equal(tasksNoCat[1].title, 'Task without Category 1')
744
+ assert.equal(tasksNoCat[2].title, 'Task without Category 2')
745
+ })
746
+ })
747
747
 
748
748
  describe('Position Field Validation', () => {
749
749
  it('should ignore manually set position values', async () => {
750
- const category = await api.resources.categories.post({ name: 'Test Category' });
751
-
750
+ const category = await api.resources.categories.post({ name: 'Test Category' })
751
+
752
752
  // Try to set position manually on create
753
753
  const task1 = await api.resources.tasks.post({
754
754
  title: 'Task 1',
755
755
  category: category.id,
756
756
  position: 'ZZZZ' // Should be ignored
757
- });
758
-
759
- assert.notEqual(task1.position, 'ZZZZ');
760
- assert.equal(task1.position, 'a0'); // First item gets 'a0'
761
-
757
+ })
758
+
759
+ assert.notEqual(task1.position, 'ZZZZ')
760
+ assert.equal(task1.position, 'a0') // First item gets 'a0'
761
+
762
762
  // Try to set position manually on update
763
763
  const task2 = await api.resources.tasks.post({
764
764
  title: 'Task 2',
765
765
  category: category.id
766
- });
767
-
768
- const originalPosition = task2.position;
769
-
766
+ })
767
+
768
+ const originalPosition = task2.position
769
+
770
770
  await api.resources.tasks.patch({
771
771
  id: task2.id,
772
772
  position: 'AAAA' // Should be ignored
773
- });
774
-
775
- const updated = await api.resources.tasks.get({ id: task2.id });
776
- assert.equal(updated.position, originalPosition); // Position unchanged
777
- });
773
+ })
774
+
775
+ const updated = await api.resources.tasks.get({ id: task2.id })
776
+ assert.equal(updated.position, originalPosition) // Position unchanged
777
+ })
778
778
 
779
779
  it('should handle both position and beforeId in update', async () => {
780
- const category = await api.resources.categories.post({ name: 'Test Category' });
781
-
780
+ const category = await api.resources.categories.post({ name: 'Test Category' })
781
+
782
782
  const task1 = await api.resources.tasks.post({
783
783
  title: 'Task 1',
784
784
  category: category.id
785
- });
786
-
785
+ })
786
+
787
787
  const task2 = await api.resources.tasks.post({
788
788
  title: 'Task 2',
789
789
  category: category.id
790
- });
791
-
790
+ })
791
+
792
792
  const task3 = await api.resources.tasks.post({
793
793
  title: 'Task 3',
794
794
  category: category.id
795
- });
796
-
795
+ })
796
+
797
797
  // Update with both position (ignored) and beforeId (used)
798
798
  await api.resources.tasks.patch({
799
799
  id: task3.id,
800
800
  position: 'XXXX', // Should be ignored
801
801
  beforeId: task1.id // Should be used
802
- });
803
-
802
+ })
803
+
804
804
  const { data: tasks } = await api.resources.tasks.query({
805
805
  queryParams: {
806
806
  filters: { category: category.id },
807
807
  sort: ['position']
808
808
  }
809
- });
810
-
809
+ })
810
+
811
811
  // Task 3 should now be before Task 1
812
- assert.equal(tasks[0].title, 'Task 3');
813
- assert.equal(tasks[1].title, 'Task 1');
814
- assert.equal(tasks[2].title, 'Task 2');
815
- assert.notEqual(tasks[0].position, 'XXXX');
816
- });
817
- });
812
+ assert.equal(tasks[0].title, 'Task 3')
813
+ assert.equal(tasks[1].title, 'Task 1')
814
+ assert.equal(tasks[2].title, 'Task 2')
815
+ assert.notEqual(tasks[0].position, 'XXXX')
816
+ })
817
+ })
818
818
 
819
819
  describe('Performance and Stress Tests', () => {
820
820
  it('should handle large number of tasks efficiently', async () => {
821
- const category = await api.resources.categories.post({ name: 'Test Category' });
822
-
821
+ const category = await api.resources.categories.post({ name: 'Test Category' })
822
+
823
823
  // Create 100 tasks
824
824
  for (let i = 0; i < 100; i++) {
825
825
  await api.resources.tasks.post({
826
826
  title: `Task ${i.toString().padStart(3, '0')}`,
827
827
  category: category.id
828
- });
828
+ })
829
829
  }
830
-
830
+
831
831
  // Query with position sort
832
832
  const { data: tasks } = await api.resources.tasks.query({
833
833
  queryParams: {
@@ -835,22 +835,22 @@ describe('Extended Positioning Plugin Tests', { timeout: 30000 }, () => {
835
835
  sort: ['position'],
836
836
  page: { size: 100 }
837
837
  }
838
- });
839
-
840
- assert.equal(tasks.length, 100);
841
-
838
+ })
839
+
840
+ assert.equal(tasks.length, 100)
841
+
842
842
  // Verify all are in correct order
843
843
  for (let i = 0; i < tasks.length; i++) {
844
- assert.equal(tasks[i].title, `Task ${i.toString().padStart(3, '0')}`);
844
+ assert.equal(tasks[i].title, `Task ${i.toString().padStart(3, '0')}`)
845
845
  }
846
-
846
+
847
847
  // Insert task in the middle
848
848
  const middleTask = await api.resources.tasks.post({
849
849
  title: 'Middle Task',
850
850
  category: category.id,
851
851
  beforeId: tasks[50].id
852
- });
853
-
852
+ })
853
+
854
854
  // Verify it's positioned correctly
855
855
  const { data: updatedTasks } = await api.resources.tasks.query({
856
856
  queryParams: {
@@ -858,27 +858,27 @@ describe('Extended Positioning Plugin Tests', { timeout: 30000 }, () => {
858
858
  sort: ['position'],
859
859
  page: { size: 102 }
860
860
  }
861
- });
862
-
863
- const middleIndex = updatedTasks.findIndex(t => t.title === 'Middle Task');
864
- assert.equal(middleIndex, 50);
865
- assert(updatedTasks[49].position < middleTask.position);
866
- assert(middleTask.position < updatedTasks[51].position);
867
- });
861
+ })
862
+
863
+ const middleIndex = updatedTasks.findIndex(t => t.title === 'Middle Task')
864
+ assert.equal(middleIndex, 50)
865
+ assert(updatedTasks[49].position < middleTask.position)
866
+ assert(middleTask.position < updatedTasks[51].position)
867
+ })
868
868
 
869
869
  it('should maintain consistency under concurrent updates', async () => {
870
- const category = await api.resources.categories.post({ name: 'Test Category' });
871
-
870
+ const category = await api.resources.categories.post({ name: 'Test Category' })
871
+
872
872
  // Create initial tasks
873
- const tasks = [];
873
+ const tasks = []
874
874
  for (let i = 0; i < 20; i++) {
875
875
  const task = await api.resources.tasks.post({
876
876
  title: `Task ${i}`,
877
877
  category: category.id
878
- });
879
- tasks.push(task);
878
+ })
879
+ tasks.push(task)
880
880
  }
881
-
881
+
882
882
  // Perform concurrent position updates
883
883
  const updates = [
884
884
  api.resources.tasks.patch({ id: tasks[5].id, beforeId: tasks[0].id }),
@@ -886,30 +886,30 @@ describe('Extended Positioning Plugin Tests', { timeout: 30000 }, () => {
886
886
  api.resources.tasks.patch({ id: tasks[15].id, beforeId: tasks[0].id }),
887
887
  api.resources.tasks.patch({ id: tasks[19].id, beforeId: 'FIRST' }),
888
888
  api.resources.tasks.patch({ id: tasks[1].id, beforeId: null }) // Move to end
889
- ];
890
-
891
- await Promise.all(updates);
892
-
889
+ ]
890
+
891
+ await Promise.all(updates)
892
+
893
893
  // Verify final state
894
894
  const { data: finalTasks } = await api.resources.tasks.query({
895
895
  queryParams: {
896
896
  filters: { category: category.id },
897
897
  sort: ['position']
898
898
  }
899
- });
900
-
899
+ })
900
+
901
901
  // All tasks should still exist
902
- assert.equal(finalTasks.length, 20);
903
-
902
+ assert.equal(finalTasks.length, 20)
903
+
904
904
  // All positions should be unique
905
- const positions = finalTasks.map(t => t.position);
906
- const uniquePositions = [...new Set(positions)];
907
- assert.equal(uniquePositions.length, 20);
908
-
905
+ const positions = finalTasks.map(t => t.position)
906
+ const uniquePositions = [...new Set(positions)]
907
+ assert.equal(uniquePositions.length, 20)
908
+
909
909
  // Verify ordering is consistent
910
910
  for (let i = 1; i < finalTasks.length; i++) {
911
- assert(finalTasks[i-1].position < finalTasks[i].position);
911
+ assert(finalTasks[i - 1].position < finalTasks[i].position)
912
912
  }
913
- });
914
- });
915
- });
913
+ })
914
+ })
915
+ })