@tldraw/sync-core 5.3.0-internal.d205573d66cb → 5.3.0-internal.fba91ed55f6c

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.
@@ -173,8 +173,13 @@ class TestInstance {
173
173
 
174
174
  hasLoaded = false
175
175
 
176
- constructor(snapshot?: RoomSnapshot, oldSchema = schemaV1, newSchema = schemaV2) {
177
- this.server = new TestServer(newSchema, snapshot)
176
+ constructor(
177
+ snapshot?: RoomSnapshot,
178
+ oldSchema = schemaV1,
179
+ newSchema = schemaV2,
180
+ roomOpts?: ConstructorParameters<typeof TestServer<RV2>>[2]
181
+ ) {
182
+ this.server = new TestServer(newSchema, snapshot, roomOpts)
178
183
  this.oldSocketPair = new TestSocketPair('test_upgrade_old', this.server)
179
184
  this.newSocketPair = new TestSocketPair('test_upgrade_new', this.server)
180
185
 
@@ -1232,3 +1237,93 @@ describe('when the client is the same version', () => {
1232
1237
  } satisfies TLSocketServerSentEvent<RV2>)
1233
1238
  })
1234
1239
  })
1240
+
1241
+ describe('record authorizers see server-schema records', () => {
1242
+ function makeInstance(authorizeUser: (args: any) => any) {
1243
+ return new TestInstance(undefined, schemaV1, schemaV2, {
1244
+ authorizeRecord: { user: authorizeUser },
1245
+ })
1246
+ }
1247
+
1248
+ it('a put from an old client reaches the authorizer up-migrated', () => {
1249
+ const seen: any[] = []
1250
+ const t = makeInstance((args: any) => {
1251
+ seen.push(structuredClone({ type: args.type, prev: args.prev, next: args.next }))
1252
+ return args.next
1253
+ })
1254
+ t.oldSocketPair.connect()
1255
+ t.flush()
1256
+
1257
+ const user = UserV1.create({ name: 'bob', age: 10 })
1258
+ t.oldClient.store.put([user])
1259
+ t.flush()
1260
+
1261
+ expect(seen).toHaveLength(1)
1262
+ expect(seen[0].type).toBe('create')
1263
+ expect(seen[0].next).toMatchObject({ name: 'bob', birthdate: null })
1264
+ expect(seen[0].next).not.toHaveProperty('age')
1265
+ })
1266
+
1267
+ it('a stamp applied on create survives — no migration runs after the authorizer', () => {
1268
+ const t = makeInstance((args: any) =>
1269
+ args.type === 'create' ? { ...args.next, birthdate: '2001-02-03' } : args.next
1270
+ )
1271
+ t.oldSocketPair.connect()
1272
+ t.flush()
1273
+
1274
+ const user = UserV1.create({ name: 'bob', age: 10 })
1275
+ t.oldClient.store.put([user])
1276
+ t.flush()
1277
+
1278
+ expect(t.server.storage.documents.get(user.id)?.state).toMatchObject({
1279
+ name: 'bob',
1280
+ birthdate: '2001-02-03',
1281
+ })
1282
+ })
1283
+
1284
+ it('a patch from an old client reaches the authorizer as the upgraded committed candidate', () => {
1285
+ const seen: any[] = []
1286
+ const t = makeInstance((args: any) => {
1287
+ seen.push(structuredClone({ type: args.type, prev: args.prev, next: args.next }))
1288
+ return args.next
1289
+ })
1290
+ t.oldSocketPair.connect()
1291
+ t.newSocketPair.connect()
1292
+ t.flush()
1293
+
1294
+ const user = UserV2.create({ name: 'bob', birthdate: '2022-01-09' })
1295
+ t.newClient.store.put([user])
1296
+ t.flush()
1297
+ seen.length = 0
1298
+
1299
+ t.oldClient.store.update(user.id as any, (u: any) => ({ ...u, name: 'bobby' }))
1300
+ t.flush()
1301
+
1302
+ expect(seen).toHaveLength(1)
1303
+ expect(seen[0].type).toBe('update')
1304
+ expect(seen[0].prev).toMatchObject({ name: 'bob', birthdate: '2022-01-09' })
1305
+ // The down→patch→up round-trip through the lossy v1 schema resets birthdate to null.
1306
+ // The authorizer must see exactly what will be committed — not a preview mixing the
1307
+ // server record with the raw v1 diff (which would still show the old birthdate).
1308
+ expect(seen[0].next).toMatchObject({ name: 'bobby', birthdate: null })
1309
+ expect(seen[0].next).not.toHaveProperty('age')
1310
+ })
1311
+
1312
+ it('vetoes based on the server-schema record', () => {
1313
+ const t = makeInstance((args: any) =>
1314
+ args.type === 'update' && args.next.name === 'forged' ? null : args.next
1315
+ )
1316
+ t.oldSocketPair.connect()
1317
+ t.newSocketPair.connect()
1318
+ t.flush()
1319
+
1320
+ const user = UserV2.create({ name: 'bob', birthdate: '2022-01-09' })
1321
+ t.newClient.store.put([user])
1322
+ t.flush()
1323
+
1324
+ t.oldClient.store.update(user.id as any, (u: any) => ({ ...u, name: 'forged' }))
1325
+ t.flush()
1326
+
1327
+ expect(t.server.storage.documents.get(user.id)?.state).toMatchObject({ name: 'bob' })
1328
+ })
1329
+ })