@platformatic/sql-mapper 0.11.0 → 0.12.0

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.
package/lib/entity.js CHANGED
@@ -7,6 +7,12 @@ const {
7
7
  tableName,
8
8
  sanitizeLimit
9
9
  } = require('./utils')
10
+ const { singularize } = require('inflected')
11
+
12
+ function lowerCaseFirst (str) {
13
+ str = str.toString()
14
+ return str.charAt(0).toLowerCase() + str.slice(1)
15
+ }
10
16
 
11
17
  function createMapper (defaultDb, sql, log, table, fields, primaryKeys, relations, queries, autoTimestamp, schema, useSchemaInName, limitConfig) {
12
18
  /* istanbul ignore next */ // Ignoring because this won't be fully covered by DB not supporting schemas (SQLite)
@@ -342,6 +348,17 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
342
348
  if (autoTimestamp && (column.column_name === autoTimestamp.createdAt || column.column_name === autoTimestamp.updatedAt)) {
343
349
  acc[column.column_name].autoTimestamp = true
344
350
  }
351
+
352
+ // To get generated information
353
+ /* istanbul ignore next */
354
+ if (db.isPg) {
355
+ acc[column.column_name].isGenerated = column.is_generated !== 'NEVER'
356
+ } else if (db.isSQLite) {
357
+ acc[column.column_name].isGenerated = column.is_generated === 'YES'
358
+ } else {
359
+ acc[column.column_name].isGenerated = column.is_generated.includes('GENERATED')
360
+ }
361
+
345
362
  return acc
346
363
  }, {})
347
364
 
@@ -362,6 +379,17 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
362
379
  const constraintsList = await queries.listConstraints(db, sql, table, schema)
363
380
  const primaryKeys = new Set()
364
381
 
382
+ /* istanbul ignore next */
383
+ function checkSQLitePrimaryKey (constraint) {
384
+ if (db.isSQLite) {
385
+ const validTypes = ['integer', 'uuid', 'serial']
386
+ const pkType = fields[constraint.column_name].sqlType.toLowerCase()
387
+ if (!validTypes.includes(pkType)) {
388
+ throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`)
389
+ }
390
+ }
391
+ }
392
+
365
393
  for (const constraint of constraintsList) {
366
394
  const field = fields[constraint.column_name]
367
395
 
@@ -377,23 +405,54 @@ async function buildEntity (db, sql, log, table, queries, autoTimestamp, schema,
377
405
  if (constraint.constraint_type === 'PRIMARY KEY') {
378
406
  primaryKeys.add(constraint.column_name)
379
407
  // Check for SQLite typeless PK
380
- /* istanbul ignore next */
381
- if (db.isSQLite) {
382
- const validTypes = ['integer', 'uuid', 'serial']
383
- const pkType = fields[constraint.column_name].sqlType.toLowerCase()
384
- if (!validTypes.includes(pkType)) {
385
- throw new Error(`Invalid Primary Key type. Expected "integer", found "${pkType}"`)
386
- }
387
- }
408
+ checkSQLitePrimaryKey(constraint)
388
409
  field.primaryKey = true
389
410
  }
390
411
 
391
412
  if (constraint.constraint_type === 'FOREIGN KEY') {
392
413
  field.foreignKey = true
414
+
415
+ // we need to ignore for coverage here becasue cannot be covered with sqlite (no schema support)
416
+ // istanbul ignore next
417
+ const foreignEntityName = singularize(camelcase(useSchemaInName ? camelcase(`${constraint.foreign_table_schema} ${constraint.foreign_table_name}`) : constraint.foreign_table_name))
418
+ // istanbul ignore next
419
+ const entityName = singularize(camelcase(useSchemaInName ? camelcase(`${constraint.table_schema} ${constraint.table_name}`) : constraint.table_name))
420
+ // istanbul ignore next
421
+ const loweredTableWithSchemaName = lowerCaseFirst(useSchemaInName ? camelcase(`${constraint.table_schema} ${camelcase(constraint.table_name)}`) : camelcase(constraint.table_name))
422
+ constraint.loweredTableWithSchemaName = loweredTableWithSchemaName
423
+ constraint.foreignEntityName = foreignEntityName
424
+ constraint.entityName = entityName
393
425
  currentRelations.push(constraint)
394
426
  }
395
427
  }
396
428
 
429
+ if (primaryKeys.size === 0) {
430
+ let found = false
431
+ for (const constraint of constraintsList) {
432
+ const field = fields[constraint.column_name]
433
+
434
+ /* istanbul ignore else */
435
+ if (constraint.constraint_type === 'UNIQUE') {
436
+ field.unique = true
437
+
438
+ /* istanbul ignore else */
439
+ if (!found) {
440
+ // Check for SQLite typeless PK
441
+ /* istanbul ignore next */
442
+ try {
443
+ checkSQLitePrimaryKey(constraint)
444
+ } catch {
445
+ continue
446
+ }
447
+
448
+ primaryKeys.add(constraint.column_name)
449
+ field.primaryKey = true
450
+ found = true
451
+ }
452
+ }
453
+ }
454
+ }
455
+
397
456
  const entity = createMapper(db, sql, log, table, fields, primaryKeys, currentRelations, queries, autoTimestamp, schema, useSchemaInName, limitConfig)
398
457
  entity.relations = currentRelations
399
458
 
@@ -23,7 +23,7 @@ async function listTables (db, sql, schemas) {
23
23
 
24
24
  async function listColumns (db, sql, table, schema) {
25
25
  const query = sql`
26
- SELECT column_name as column_name, data_type as udt_name, is_nullable as is_nullable, column_type as column_type
26
+ SELECT column_name as column_name, data_type as udt_name, is_nullable as is_nullable, column_type as column_type, extra as is_generated
27
27
  FROM information_schema.columns
28
28
  WHERE table_name = ${table}
29
29
  AND table_schema = ${schema}
@@ -33,7 +33,7 @@ async function listColumns (db, sql, table, schema) {
33
33
 
34
34
  async function listConstraints (db, sql, table, schema) {
35
35
  const query = sql`
36
- SELECT TABLE_NAME as table_name, COLUMN_NAME as column_name, CONSTRAINT_TYPE as constraint_type, referenced_table_name AS foreign_table_name, referenced_column_name AS foreign_column_name
36
+ SELECT TABLE_NAME as table_name, TABLE_SCHEMA as table_schema, COLUMN_NAME as column_name, CONSTRAINT_TYPE as constraint_type, referenced_table_name AS foreign_table_name, referenced_table_schema AS foreign_table_schema, referenced_column_name AS foreign_column_name
37
37
  FROM information_schema.table_constraints t
38
38
  JOIN information_schema.key_column_usage k
39
39
  USING (constraint_name, table_schema, table_name)
package/lib/queries/pg.js CHANGED
@@ -44,7 +44,7 @@ module.exports.listTables = listTables
44
44
 
45
45
  async function listColumns (db, sql, table, schema) {
46
46
  return db.query(sql`
47
- SELECT column_name, udt_name, is_nullable
47
+ SELECT column_name, udt_name, is_nullable, is_generated
48
48
  FROM information_schema.columns
49
49
  WHERE table_name = ${table}
50
50
  AND table_schema = ${schema}
@@ -55,7 +55,7 @@ module.exports.listColumns = listColumns
55
55
 
56
56
  async function listConstraints (db, sql, table, schema) {
57
57
  const query = sql`
58
- SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name
58
+ SELECT constraints.*, usage.*, usage2.table_name AS foreign_table_name, usage2.column_name AS foreign_column_name, usage2.table_schema AS foreign_table_schema
59
59
  FROM information_schema.table_constraints constraints
60
60
  JOIN information_schema.key_column_usage usage
61
61
  ON constraints.constraint_name = usage.constraint_name
@@ -15,8 +15,10 @@ async function listTables (db, sql) {
15
15
  module.exports.listTables = listTables
16
16
 
17
17
  async function listColumns (db, sql, table) {
18
+ // pragma_table_info is not returning hidden column which tells if the column is generated or not
19
+ // therefore it is changed to pragma_table_xinfo
18
20
  const columns = await db.query(sql`
19
- SELECT * FROM pragma_table_info(${table})
21
+ SELECT * FROM pragma_table_xinfo(${table})
20
22
  `)
21
23
  for (const column of columns) {
22
24
  column.column_name = column.name
@@ -24,6 +26,8 @@ async function listColumns (db, sql, table) {
24
26
  column.udt_name = column.type.replace(/^([^(]+).*/, '$1').toLowerCase()
25
27
  // convert is_nullable
26
28
  column.is_nullable = column.notnull === 0 && column.pk === 0 ? 'YES' : 'NO'
29
+ // convert hidden to is_generated
30
+ column.is_generated = (column.hidden === 2 || column.hidden === 3) ? 'YES' : 'NO'
27
31
  }
28
32
  return columns
29
33
  }
@@ -45,6 +49,22 @@ async function listConstraints (db, sql, table) {
45
49
  })
46
50
  }
47
51
 
52
+ const indexes = await db.query(sql`
53
+ SELECT *
54
+ FROM pragma_index_list(${table}) as il
55
+ JOIN pragma_index_info(il.name) as ii
56
+ `)
57
+
58
+ for (const index of indexes) {
59
+ /* istanbul ignore else */
60
+ if (index.unique === 1) {
61
+ constraints.push({
62
+ column_name: index.name,
63
+ constraint_type: 'UNIQUE'
64
+ })
65
+ }
66
+ }
67
+
48
68
  const foreignKeys = await db.query(sql`
49
69
  SELECT *
50
70
  FROM pragma_foreign_key_list(${table})
package/mapper.js CHANGED
@@ -125,7 +125,8 @@ async function connect ({ connectionString, log, onDatabaseLoad, poolSize = 10,
125
125
  const entity = await buildEntity(db, sql, log, table, queries, autoTimestamp, schema, useSchema, ignore[table] || {}, limit)
126
126
  // Check for primary key of all entities
127
127
  if (entity.primaryKeys.size === 0) {
128
- throw Error(`Cannot find any primary keys for ${entity.name} entity: ${JSON.stringify(entity)}`)
128
+ log.warn({ table }, 'Cannot find any primary keys for table')
129
+ continue
129
130
  }
130
131
 
131
132
  entities[entity.singularName] = entity
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/sql-mapper",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "A data mapper utility for SQL databases",
5
5
  "main": "mapper.js",
6
6
  "repository": {
@@ -14,19 +14,19 @@
14
14
  },
15
15
  "homepage": "https://github.com/platformatic/platformatic#readme",
16
16
  "devDependencies": {
17
- "fastify": "^4.5.3",
17
+ "fastify": "^4.10.2",
18
18
  "snazzy": "^9.0.0",
19
19
  "standard": "^17.0.0",
20
- "tap": "^16.0.0",
20
+ "tap": "^16.3.2",
21
21
  "tsd": "^0.25.0"
22
22
  },
23
23
  "dependencies": {
24
- "@databases/mysql": "^5.2.0",
25
- "@databases/pg": "^5.3.0",
24
+ "@databases/mysql": "^5.2.1",
25
+ "@databases/pg": "^5.4.1",
26
26
  "@databases/sql": "^3.2.0",
27
27
  "@databases/sqlite": "^4.0.2",
28
- "camelcase": "^6.0.0",
29
- "fastify-plugin": "^4.1.0",
28
+ "camelcase": "^6.3.0",
29
+ "fastify-plugin": "^4.4.0",
30
30
  "inflected": "^2.1.0"
31
31
  },
32
32
  "tsd": {
@@ -717,3 +717,155 @@ test('JSON type', { skip: !(isPg || isMysql8) }, async ({ teardown, same, equal,
717
717
  }
718
718
  }), [{ id: 2, config: { foo: 'bar', bar: 'foo' } }])
719
719
  })
720
+
721
+ test('stored and virtual generated columns should return for SQLite', { skip: !(isSQLite) }, async ({ teardown, same }) => {
722
+ async function onDatabaseLoad (db, sql) {
723
+ await clear(db, sql)
724
+ teardown(() => db.dispose())
725
+
726
+ await db.query(sql`CREATE TABLE generated_test (
727
+ id INTEGER PRIMARY KEY,
728
+ test INTEGER,
729
+ test_stored INTEGER GENERATED ALWAYS AS (test*2) STORED,
730
+ test_virtual INTEGER GENERATED ALWAYS AS (test*4) VIRTUAL
731
+ );`)
732
+ }
733
+
734
+ const mapper = await connect({
735
+ connectionString: connInfo.connectionString,
736
+ log: fakeLogger,
737
+ onDatabaseLoad,
738
+ ignore: {},
739
+ hooks: {}
740
+ })
741
+
742
+ const generatedTest = mapper.entities.generatedTest
743
+
744
+ // save - new record
745
+ same(await generatedTest.save({
746
+ input: { test: 1 }
747
+ }), { id: 1, test: 1, testStored: 2, testVirtual: 4 })
748
+
749
+ // save - update
750
+ same(await generatedTest.save({
751
+ input: { id: 1, test: 2 }
752
+ }), { id: 1, test: 2, testStored: 4, testVirtual: 8 })
753
+
754
+ // insert
755
+ same(await generatedTest.insert({
756
+ inputs: [{ test: 4 }]
757
+ }), [{ id: 2, test: 4, testStored: 8, testVirtual: 16 }])
758
+
759
+ // updateMany
760
+ same(await generatedTest.updateMany({
761
+ where: {
762
+ id: {
763
+ eq: 2
764
+ }
765
+ },
766
+ input: {
767
+ test: 8
768
+ }
769
+ }), [{ id: 2, test: 8, testStored: 16, testVirtual: 32 }])
770
+ })
771
+
772
+ test('stored generated columns should return for pg', { skip: !(isPg) }, async ({ teardown, same }) => {
773
+ async function onDatabaseLoad (db, sql) {
774
+ await clear(db, sql)
775
+ teardown(() => db.dispose())
776
+
777
+ await db.query(sql`CREATE TABLE generated_test (
778
+ id SERIAL PRIMARY KEY,
779
+ test INTEGER,
780
+ test_stored INTEGER GENERATED ALWAYS AS (test*2) STORED
781
+ );`)
782
+ }
783
+
784
+ const mapper = await connect({
785
+ connectionString: connInfo.connectionString,
786
+ log: fakeLogger,
787
+ onDatabaseLoad,
788
+ ignore: {},
789
+ hooks: {}
790
+ })
791
+
792
+ const generatedTest = mapper.entities.generatedTest
793
+
794
+ // save - new record
795
+ same(await generatedTest.save({
796
+ input: { test: 1 }
797
+ }), { id: 1, test: 1, testStored: 2 })
798
+
799
+ // save - update
800
+ same(await generatedTest.save({
801
+ input: { id: 1, test: 2 }
802
+ }), { id: 1, test: 2, testStored: 4 })
803
+
804
+ // insert
805
+ same(await generatedTest.insert({
806
+ inputs: [{ test: 4 }]
807
+ }), [{ id: 2, test: 4, testStored: 8 }])
808
+
809
+ // updateMany
810
+ same(await generatedTest.updateMany({
811
+ where: {
812
+ id: {
813
+ eq: 2
814
+ }
815
+ },
816
+ input: {
817
+ test: 8
818
+ }
819
+ }), [{ id: 2, test: 8, testStored: 16 }])
820
+ })
821
+
822
+ test('stored and virtual generated columns should return for pg', { skip: (isPg || isSQLite) }, async ({ teardown, same }) => {
823
+ async function onDatabaseLoad (db, sql) {
824
+ await clear(db, sql)
825
+ teardown(() => db.dispose())
826
+
827
+ await db.query(sql`CREATE TABLE generated_test (
828
+ id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY,
829
+ test INTEGER,
830
+ test_stored INTEGER GENERATED ALWAYS AS (test*2) STORED,
831
+ test_virtual INTEGER GENERATED ALWAYS AS (test*4) VIRTUAL
832
+ );`)
833
+ }
834
+
835
+ const mapper = await connect({
836
+ connectionString: connInfo.connectionString,
837
+ log: fakeLogger,
838
+ onDatabaseLoad,
839
+ ignore: {},
840
+ hooks: {}
841
+ })
842
+
843
+ const generatedTest = mapper.entities.generatedTest
844
+
845
+ // save - new record
846
+ same(await generatedTest.save({
847
+ input: { test: 1 }
848
+ }), { id: 1, test: 1, testStored: 2, testVirtual: 4 })
849
+
850
+ // save - update
851
+ same(await generatedTest.save({
852
+ input: { id: 1, test: 2 }
853
+ }), { id: 1, test: 2, testStored: 4, testVirtual: 8 })
854
+
855
+ // insert
856
+ same(await generatedTest.insert({
857
+ inputs: [{ test: 4 }]
858
+ }), [{ id: 2, test: 4, testStored: 8, testVirtual: 16 }])
859
+
860
+ // updateMany
861
+ same(await generatedTest.updateMany({
862
+ where: {
863
+ id: {
864
+ eq: 2
865
+ }
866
+ },
867
+ input: {
868
+ test: 8
869
+ }
870
+ }), [{ id: 2, test: 8, testStored: 16, testVirtual: 32 }])
871
+ })
package/test/helper.js CHANGED
@@ -94,4 +94,9 @@ module.exports.clear = async function (db, sql) {
94
94
  await db.query(sql`DROP TABLE test2.pages`)
95
95
  } catch (err) {
96
96
  }
97
+
98
+ try {
99
+ await db.query(sql`DROP TABLE generated_test`)
100
+ } catch (err) {
101
+ }
97
102
  }
@@ -193,20 +193,3 @@ test('missing connectionString', async ({ rejects }) => {
193
193
 
194
194
  await rejects(app.ready(), /connectionString/)
195
195
  })
196
-
197
- test('throw if no primary keys', async ({ rejects, teardown }) => {
198
- async function onDatabaseLoad (db, sql) {
199
- await clear(db, sql)
200
-
201
- await db.query(sql`CREATE TABLE pages (
202
- title VARCHAR(255) NOT NULL
203
- );`)
204
- }
205
- await rejects(connect({
206
- connectionString: connInfo.connectionString,
207
- log: fakeLogger,
208
- onDatabaseLoad,
209
- ignore: {},
210
- hooks: {}
211
- }))
212
- })
@@ -0,0 +1,79 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+
5
+ const { clear, connInfo, isMysql8 } = require('./helper')
6
+ const { connect } = require('..')
7
+ const fakeLogger = {
8
+ trace: () => {},
9
+ warn: () => {},
10
+ error: () => {}
11
+ }
12
+
13
+ test('unique key', async ({ equal, not, same, teardown }) => {
14
+ async function onDatabaseLoad (db, sql) {
15
+ await clear(db, sql)
16
+ teardown(() => db.dispose())
17
+
18
+ const table = sql`
19
+ CREATE TABLE pages (
20
+ xx INTEGER DEFAULT NULL UNIQUE,
21
+ name varchar(75) DEFAULT NULL UNIQUE
22
+ );
23
+ `
24
+
25
+ await db.query(table)
26
+ }
27
+ const mapper = await connect({
28
+ connectionString: connInfo.connectionString,
29
+ log: fakeLogger,
30
+ onDatabaseLoad,
31
+ ignore: {},
32
+ hooks: {}
33
+ })
34
+ const pageEntity = mapper.entities.page
35
+ not(pageEntity, undefined)
36
+ equal(pageEntity.name, 'Page')
37
+ equal(pageEntity.singularName, 'page')
38
+ equal(pageEntity.pluralName, 'pages')
39
+ if (isMysql8) {
40
+ same(pageEntity.primaryKeys, new Set(['name']))
41
+ equal(pageEntity.camelCasedFields.name.primaryKey, true)
42
+ } else {
43
+ same(pageEntity.primaryKeys, new Set(['xx']))
44
+ equal(pageEntity.camelCasedFields.xx.primaryKey, true)
45
+ }
46
+ equal(pageEntity.camelCasedFields.xx.unique, true)
47
+ equal(pageEntity.camelCasedFields.name.unique, true)
48
+ })
49
+
50
+ test('no key', async ({ same, teardown, pass, equal, plan }) => {
51
+ plan(3)
52
+ async function onDatabaseLoad (db, sql) {
53
+ await clear(db, sql)
54
+ teardown(() => db.dispose())
55
+
56
+ const table = sql`
57
+ CREATE TABLE pages (
58
+ xx INTEGER DEFAULT NULL,
59
+ name varchar(75) DEFAULT NULL
60
+ );
61
+ `
62
+
63
+ await db.query(table)
64
+ }
65
+ const log = {
66
+ trace: () => {},
67
+ warn: (obj, str) => {
68
+ same(obj, { table: 'pages' })
69
+ equal(str, 'Cannot find any primary keys for table')
70
+ },
71
+ error: () => {}
72
+ }
73
+ const mapper = await connect({
74
+ connectionString: connInfo.connectionString,
75
+ log,
76
+ onDatabaseLoad
77
+ })
78
+ same(mapper.entities, {})
79
+ })
@@ -174,7 +174,6 @@ test('[pg] if schema is empty array, should find entities only in default \'publ
174
174
  hooks: {},
175
175
  schema: []
176
176
  })
177
-
178
177
  equal(Object.keys(mapper.entities).length, 1)
179
178
  const pageEntity = mapper.entities.page
180
179
  equal(pageEntity.name, 'Page')
@@ -318,3 +317,65 @@ test('addEntityHooks in entities with schema', { skip: isSQLite }, async ({ pass
318
317
  await entity.insert({ inputs: [{ title: 'hello' }, { title: 'world' }], fields: ['id', 'title'] })
319
318
  end()
320
319
  })
320
+
321
+ test('uses tables from different schemas with FK', { skip: isSQLite }, async ({ pass, teardown, equal }) => {
322
+ async function onDatabaseLoad (db, sql) {
323
+ await clear(db, sql)
324
+ teardown(() => db.dispose())
325
+
326
+ await db.query(sql`CREATE SCHEMA IF NOT EXISTS test1;`)
327
+ if (isMysql || isMysql8) {
328
+ await db.query(sql`CREATE TABLE IF NOT EXISTS \`test1\`.\`pages\` (
329
+ id SERIAL PRIMARY KEY,
330
+ title VARCHAR(255) NOT NULL
331
+ );`)
332
+ } else {
333
+ await db.query(sql`CREATE TABLE IF NOT EXISTS "test1"."pages" (
334
+ id SERIAL PRIMARY KEY,
335
+ title VARCHAR(255) NOT NULL
336
+ );`)
337
+ }
338
+
339
+ await db.query(sql`CREATE SCHEMA IF NOT EXISTS test2;`)
340
+
341
+ if (isMysql || isMysql8) {
342
+ await db.query(sql`CREATE TABLE IF NOT EXISTS \`test2\`.\`users\` (
343
+ id SERIAL PRIMARY KEY,
344
+ username VARCHAR(255) NOT NULL,
345
+ page_id BIGINT UNSIGNED,
346
+ FOREIGN KEY(page_id) REFERENCES test1.pages(id) ON DELETE CASCADE
347
+ );`)
348
+ } else {
349
+ await db.query(sql`CREATE TABLE IF NOT EXISTS "test2"."users" (
350
+ id SERIAL PRIMARY KEY,
351
+ username VARCHAR(255) NOT NULL,
352
+ page_id integer REFERENCES test1.pages(id)
353
+ );`)
354
+ }
355
+ }
356
+ const mapper = await connect({
357
+ connectionString: connInfo.connectionString,
358
+ log: fakeLogger,
359
+ onDatabaseLoad,
360
+ ignore: {},
361
+ hooks: {},
362
+ schema: ['test1', 'test2']
363
+ })
364
+ const pageEntity = mapper.entities.test1Page
365
+ equal(pageEntity.name, 'Test1Page')
366
+ equal(pageEntity.singularName, 'test1Page')
367
+ equal(pageEntity.pluralName, 'test1Pages')
368
+ equal(pageEntity.schema, 'test1')
369
+ equal(pageEntity.relations.length, 0)
370
+
371
+ const userEntity = mapper.entities.test2User
372
+ equal(userEntity.name, 'Test2User')
373
+ equal(userEntity.singularName, 'test2User')
374
+ equal(userEntity.pluralName, 'test2Users')
375
+ equal(userEntity.schema, 'test2')
376
+ equal(userEntity.relations.length, 1)
377
+ const userRelation = userEntity.relations[0]
378
+ equal(userRelation.foreignEntityName, 'test1Page')
379
+ equal(userRelation.entityName, 'test2User')
380
+ pass()
381
+ })