@expo/entity-database-adapter-knex-testing-utils 0.57.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.
@@ -0,0 +1,301 @@
1
+ import {
2
+ computeIfAbsent,
3
+ EntityConfiguration,
4
+ FieldTransformerMap,
5
+ getDatabaseFieldForEntityField,
6
+ IntField,
7
+ mapMap,
8
+ StringField,
9
+ transformFieldsToDatabaseObject,
10
+ } from '@expo/entity';
11
+ import {
12
+ BasePostgresEntityDatabaseAdapter,
13
+ NullsOrdering,
14
+ OrderByOrdering,
15
+ SQLFragment,
16
+ TableFieldMultiValueEqualityCondition,
17
+ TableFieldSingleValueEqualityCondition,
18
+ TableOrderByClause,
19
+ TableQuerySelectionModifiers,
20
+ } from '@expo/entity-database-adapter-knex';
21
+ import invariant from 'invariant';
22
+ import { v7 as uuidv7 } from 'uuid';
23
+
24
+ export class StubPostgresDatabaseAdapter<
25
+ TFields extends Record<string, any>,
26
+ TIDField extends keyof TFields,
27
+ > extends BasePostgresEntityDatabaseAdapter<TFields, TIDField> {
28
+ constructor(
29
+ private readonly entityConfiguration2: EntityConfiguration<TFields, TIDField>,
30
+ private readonly dataStore: Map<string, Readonly<{ [key: string]: any }>[]>,
31
+ ) {
32
+ super(entityConfiguration2);
33
+ }
34
+
35
+ public static convertFieldObjectsToDataStore<
36
+ TFields extends Record<string, any>,
37
+ TIDField extends keyof TFields,
38
+ >(
39
+ entityConfiguration: EntityConfiguration<TFields, TIDField>,
40
+ dataStore: Map<string, Readonly<TFields>[]>,
41
+ ): Map<string, Readonly<{ [key: string]: any }>[]> {
42
+ return mapMap(dataStore, (objectsForTable) =>
43
+ objectsForTable.map((objectForTable) =>
44
+ transformFieldsToDatabaseObject(entityConfiguration, new Map(), objectForTable),
45
+ ),
46
+ );
47
+ }
48
+
49
+ public getObjectCollectionForTable(tableName: string): { [key: string]: any }[] {
50
+ return computeIfAbsent(this.dataStore, tableName, () => []);
51
+ }
52
+
53
+ protected getFieldTransformerMap(): FieldTransformerMap {
54
+ return new Map();
55
+ }
56
+
57
+ private static uniqBy<T>(a: T[], keyExtractor: (k: T) => string): T[] {
58
+ const seen = new Set();
59
+ return a.filter((item) => {
60
+ const k = keyExtractor(item);
61
+ if (seen.has(k)) {
62
+ return false;
63
+ }
64
+ seen.add(k);
65
+ return true;
66
+ });
67
+ }
68
+
69
+ protected async fetchManyWhereInternalAsync(
70
+ _queryInterface: any,
71
+ tableName: string,
72
+ tableColumns: readonly string[],
73
+ tableTuples: (readonly any[])[],
74
+ ): Promise<object[]> {
75
+ const objectCollection = this.getObjectCollectionForTable(tableName);
76
+ const results = StubPostgresDatabaseAdapter.uniqBy(tableTuples, (tuple) =>
77
+ tuple.join(':'),
78
+ ).reduce(
79
+ (acc, tableTuple) => {
80
+ return acc.concat(
81
+ objectCollection.filter((obj) => {
82
+ return tableColumns.every((tableColumn, index) => {
83
+ return obj[tableColumn] === tableTuple[index];
84
+ });
85
+ }),
86
+ );
87
+ },
88
+ [] as { [key: string]: any }[],
89
+ );
90
+ return [...results];
91
+ }
92
+
93
+ protected async fetchOneWhereInternalAsync(
94
+ queryInterface: any,
95
+ tableName: string,
96
+ tableColumns: readonly string[],
97
+ tableTuple: readonly any[],
98
+ ): Promise<object | null> {
99
+ const results = await this.fetchManyWhereInternalAsync(
100
+ queryInterface,
101
+ tableName,
102
+ tableColumns,
103
+ [tableTuple],
104
+ );
105
+ return results[0] ?? null;
106
+ }
107
+
108
+ private static compareByOrderBys<TFields extends Record<string, any>>(
109
+ orderBys: TableOrderByClause<TFields>[],
110
+ objectA: { [key: string]: any },
111
+ objectB: { [key: string]: any },
112
+ ): 0 | 1 | -1 {
113
+ if (orderBys.length === 0) {
114
+ return 0;
115
+ }
116
+
117
+ const currentOrderBy = orderBys[0]!;
118
+ if (!('columnName' in currentOrderBy)) {
119
+ throw new Error('SQL fragment order by not supported for StubDatabaseAdapter');
120
+ }
121
+ const aField = objectA[currentOrderBy.columnName];
122
+ const bField = objectB[currentOrderBy.columnName];
123
+
124
+ // Determine effective nulls ordering:
125
+ // - If explicitly set, use that
126
+ // - Otherwise use PostgreSQL defaults: NULLS LAST for ASC, NULLS FIRST for DESC
127
+ const nullsFirst =
128
+ currentOrderBy.nulls !== undefined
129
+ ? currentOrderBy.nulls === NullsOrdering.FIRST
130
+ : currentOrderBy.order === OrderByOrdering.DESCENDING;
131
+
132
+ if (aField === null && bField === null) {
133
+ return this.compareByOrderBys(orderBys.slice(1), objectA, objectB);
134
+ } else if (aField === null) {
135
+ return nullsFirst ? -1 : 1;
136
+ } else if (bField === null) {
137
+ return nullsFirst ? 1 : -1;
138
+ }
139
+
140
+ switch (currentOrderBy.order) {
141
+ case OrderByOrdering.DESCENDING: {
142
+ return aField > bField
143
+ ? -1
144
+ : aField < bField
145
+ ? 1
146
+ : this.compareByOrderBys(orderBys.slice(1), objectA, objectB);
147
+ }
148
+ case OrderByOrdering.ASCENDING: {
149
+ return bField > aField
150
+ ? -1
151
+ : bField < aField
152
+ ? 1
153
+ : this.compareByOrderBys(orderBys.slice(1), objectA, objectB);
154
+ }
155
+ }
156
+ }
157
+
158
+ protected async fetchManyByFieldEqualityConjunctionInternalAsync(
159
+ _queryInterface: any,
160
+ tableName: string,
161
+ tableFieldSingleValueEqualityOperands: TableFieldSingleValueEqualityCondition[],
162
+ tableFieldMultiValueEqualityOperands: TableFieldMultiValueEqualityCondition[],
163
+ querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
164
+ ): Promise<object[]> {
165
+ let filteredObjects = this.getObjectCollectionForTable(tableName);
166
+ for (const { tableField, tableValue } of tableFieldSingleValueEqualityOperands) {
167
+ filteredObjects = filteredObjects.filter((obj) => obj[tableField] === tableValue);
168
+ }
169
+
170
+ for (const { tableField, tableValues } of tableFieldMultiValueEqualityOperands) {
171
+ filteredObjects = filteredObjects.filter((obj) => tableValues.includes(obj[tableField]));
172
+ }
173
+
174
+ const orderBy = querySelectionModifiers.orderBy;
175
+ if (orderBy !== undefined) {
176
+ filteredObjects = filteredObjects.sort((a, b) =>
177
+ StubPostgresDatabaseAdapter.compareByOrderBys(orderBy, a, b),
178
+ );
179
+ }
180
+
181
+ const offset = querySelectionModifiers.offset;
182
+ if (offset !== undefined) {
183
+ filteredObjects = filteredObjects.slice(offset);
184
+ }
185
+
186
+ const limit = querySelectionModifiers.limit;
187
+ if (limit !== undefined) {
188
+ filteredObjects = filteredObjects.slice(0, 0 + limit);
189
+ }
190
+
191
+ return filteredObjects;
192
+ }
193
+
194
+ protected fetchManyByRawWhereClauseInternalAsync(
195
+ _queryInterface: any,
196
+ _tableName: string,
197
+ _rawWhereClause: string,
198
+ _bindings: object | any[],
199
+ _querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
200
+ ): Promise<object[]> {
201
+ throw new Error('Raw WHERE clauses not supported for StubDatabaseAdapter');
202
+ }
203
+
204
+ protected fetchManyBySQLFragmentInternalAsync(
205
+ _queryInterface: any,
206
+ _tableName: string,
207
+ _sqlFragment: SQLFragment<TFields>,
208
+ _querySelectionModifiers: TableQuerySelectionModifiers<TFields>,
209
+ ): Promise<object[]> {
210
+ throw new Error('SQL fragments not supported for StubDatabaseAdapter');
211
+ }
212
+
213
+ private generateRandomID(): any {
214
+ const idSchemaField = this.entityConfiguration2.schema.get(this.entityConfiguration2.idField);
215
+ invariant(
216
+ idSchemaField,
217
+ `No schema field found for ${String(this.entityConfiguration2.idField)}`,
218
+ );
219
+ if (idSchemaField instanceof StringField) {
220
+ return uuidv7();
221
+ } else if (idSchemaField instanceof IntField) {
222
+ return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
223
+ } else {
224
+ throw new Error(
225
+ `Unsupported ID type for StubPostgresDatabaseAdapter: ${idSchemaField.constructor.name}`,
226
+ );
227
+ }
228
+ }
229
+
230
+ protected async insertInternalAsync(
231
+ _queryInterface: any,
232
+ tableName: string,
233
+ object: object,
234
+ ): Promise<object[]> {
235
+ const objectCollection = this.getObjectCollectionForTable(tableName);
236
+
237
+ const idField = getDatabaseFieldForEntityField(
238
+ this.entityConfiguration2,
239
+ this.entityConfiguration2.idField,
240
+ );
241
+ const objectToInsert = {
242
+ [idField]: this.generateRandomID(),
243
+ ...object,
244
+ };
245
+ objectCollection.push(objectToInsert);
246
+ return [objectToInsert];
247
+ }
248
+
249
+ protected async updateInternalAsync(
250
+ _queryInterface: any,
251
+ tableName: string,
252
+ tableIdField: string,
253
+ id: any,
254
+ object: object,
255
+ ): Promise<object[]> {
256
+ // SQL does not support empty updates, mirror behavior here for better test simulation
257
+ if (Object.keys(object).length === 0) {
258
+ throw new Error(`Empty update (${tableIdField} = ${id})`);
259
+ }
260
+
261
+ const objectCollection = this.getObjectCollectionForTable(tableName);
262
+
263
+ const objectIndex = objectCollection.findIndex((obj) => {
264
+ return obj[tableIdField] === id;
265
+ });
266
+
267
+ // SQL updates to a nonexistent row succeed but affect 0 rows,
268
+ // mirror that behavior here for better test simulation
269
+ if (objectIndex < 0) {
270
+ return [];
271
+ }
272
+
273
+ objectCollection[objectIndex] = {
274
+ ...objectCollection[objectIndex],
275
+ ...object,
276
+ };
277
+ return [objectCollection[objectIndex]];
278
+ }
279
+
280
+ protected async deleteInternalAsync(
281
+ _queryInterface: any,
282
+ tableName: string,
283
+ tableIdField: string,
284
+ id: any,
285
+ ): Promise<number> {
286
+ const objectCollection = this.getObjectCollectionForTable(tableName);
287
+
288
+ const objectIndex = objectCollection.findIndex((obj) => {
289
+ return obj[tableIdField] === id;
290
+ });
291
+
292
+ // SQL deletes to a nonexistent row succeed and affect 0 rows,
293
+ // mirror that behavior here for better test simulation
294
+ if (objectIndex < 0) {
295
+ return 0;
296
+ }
297
+
298
+ objectCollection.splice(objectIndex, 1);
299
+ return 1;
300
+ }
301
+ }
@@ -0,0 +1,17 @@
1
+ import {
2
+ EntityConfiguration,
3
+ EntityDatabaseAdapter,
4
+ IEntityDatabaseAdapterProvider,
5
+ } from '@expo/entity';
6
+
7
+ import { StubPostgresDatabaseAdapter } from './StubPostgresDatabaseAdapter';
8
+
9
+ export class StubPostgresDatabaseAdapterProvider implements IEntityDatabaseAdapterProvider {
10
+ private readonly objectCollection = new Map();
11
+
12
+ getDatabaseAdapter<TFields extends Record<string, any>, TIDField extends keyof TFields>(
13
+ entityConfiguration: EntityConfiguration<TFields, TIDField>,
14
+ ): EntityDatabaseAdapter<TFields, TIDField> {
15
+ return new StubPostgresDatabaseAdapter(entityConfiguration, this.objectCollection);
16
+ }
17
+ }
@@ -0,0 +1,62 @@
1
+ import {
2
+ AlwaysAllowPrivacyPolicyRule,
3
+ DateField,
4
+ Entity,
5
+ EntityCompanionDefinition,
6
+ EntityConfiguration,
7
+ EntityPrivacyPolicy,
8
+ ViewerContext,
9
+ } from '@expo/entity';
10
+
11
+ export type DateIDTestFields = {
12
+ id: Date;
13
+ };
14
+
15
+ export const dateIDTestEntityConfiguration = new EntityConfiguration<DateIDTestFields, 'id'>({
16
+ idField: 'id',
17
+ tableName: 'simple_test_entity_should_not_write_to_db',
18
+ schema: {
19
+ id: new DateField({
20
+ columnName: 'custom_id',
21
+ cache: true,
22
+ }),
23
+ },
24
+ databaseAdapterFlavor: 'postgres',
25
+ cacheAdapterFlavor: 'redis',
26
+ });
27
+
28
+ export class DateIDTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
29
+ DateIDTestFields,
30
+ 'id',
31
+ ViewerContext,
32
+ DateIDTestEntity
33
+ > {
34
+ protected override readonly readRules = [
35
+ new AlwaysAllowPrivacyPolicyRule<DateIDTestFields, 'id', ViewerContext, DateIDTestEntity>(),
36
+ ];
37
+ protected override readonly createRules = [
38
+ new AlwaysAllowPrivacyPolicyRule<DateIDTestFields, 'id', ViewerContext, DateIDTestEntity>(),
39
+ ];
40
+ protected override readonly updateRules = [
41
+ new AlwaysAllowPrivacyPolicyRule<DateIDTestFields, 'id', ViewerContext, DateIDTestEntity>(),
42
+ ];
43
+ protected override readonly deleteRules = [
44
+ new AlwaysAllowPrivacyPolicyRule<DateIDTestFields, 'id', ViewerContext, DateIDTestEntity>(),
45
+ ];
46
+ }
47
+
48
+ export class DateIDTestEntity extends Entity<DateIDTestFields, 'id', ViewerContext> {
49
+ static defineCompanionDefinition(): EntityCompanionDefinition<
50
+ DateIDTestFields,
51
+ 'id',
52
+ ViewerContext,
53
+ DateIDTestEntity,
54
+ DateIDTestEntityPrivacyPolicy
55
+ > {
56
+ return {
57
+ entityClass: DateIDTestEntity,
58
+ entityConfiguration: dateIDTestEntityConfiguration,
59
+ privacyPolicyClass: DateIDTestEntityPrivacyPolicy,
60
+ };
61
+ }
62
+ }
@@ -0,0 +1,95 @@
1
+ import {
2
+ AlwaysAllowPrivacyPolicyRule,
3
+ Entity,
4
+ EntityCompanionDefinition,
5
+ EntityConfiguration,
6
+ EntityPrivacyPolicy,
7
+ UUIDField,
8
+ ViewerContext,
9
+ } from '@expo/entity';
10
+
11
+ export type SimpleTestFields = {
12
+ id: string;
13
+ };
14
+
15
+ export type SimpleTestFieldSelection = keyof SimpleTestFields;
16
+
17
+ export const simpleTestEntityConfiguration = new EntityConfiguration<SimpleTestFields, 'id'>({
18
+ idField: 'id',
19
+ tableName: 'simple_test_entity_should_not_write_to_db',
20
+ schema: {
21
+ id: new UUIDField({
22
+ columnName: 'custom_id',
23
+ cache: true,
24
+ }),
25
+ },
26
+ databaseAdapterFlavor: 'postgres',
27
+ cacheAdapterFlavor: 'redis',
28
+ });
29
+
30
+ export class SimpleTestEntityPrivacyPolicy extends EntityPrivacyPolicy<
31
+ SimpleTestFields,
32
+ 'id',
33
+ ViewerContext,
34
+ SimpleTestEntity,
35
+ SimpleTestFieldSelection
36
+ > {
37
+ protected override readonly readRules = [
38
+ new AlwaysAllowPrivacyPolicyRule<
39
+ SimpleTestFields,
40
+ 'id',
41
+ ViewerContext,
42
+ SimpleTestEntity,
43
+ SimpleTestFieldSelection
44
+ >(),
45
+ ];
46
+ protected override readonly createRules = [
47
+ new AlwaysAllowPrivacyPolicyRule<
48
+ SimpleTestFields,
49
+ 'id',
50
+ ViewerContext,
51
+ SimpleTestEntity,
52
+ SimpleTestFieldSelection
53
+ >(),
54
+ ];
55
+ protected override readonly updateRules = [
56
+ new AlwaysAllowPrivacyPolicyRule<
57
+ SimpleTestFields,
58
+ 'id',
59
+ ViewerContext,
60
+ SimpleTestEntity,
61
+ SimpleTestFieldSelection
62
+ >(),
63
+ ];
64
+ protected override readonly deleteRules = [
65
+ new AlwaysAllowPrivacyPolicyRule<
66
+ SimpleTestFields,
67
+ 'id',
68
+ ViewerContext,
69
+ SimpleTestEntity,
70
+ SimpleTestFieldSelection
71
+ >(),
72
+ ];
73
+ }
74
+
75
+ export class SimpleTestEntity extends Entity<
76
+ SimpleTestFields,
77
+ 'id',
78
+ ViewerContext,
79
+ SimpleTestFieldSelection
80
+ > {
81
+ static defineCompanionDefinition(): EntityCompanionDefinition<
82
+ SimpleTestFields,
83
+ 'id',
84
+ ViewerContext,
85
+ SimpleTestEntity,
86
+ SimpleTestEntityPrivacyPolicy,
87
+ SimpleTestFieldSelection
88
+ > {
89
+ return {
90
+ entityClass: SimpleTestEntity,
91
+ entityConfiguration: simpleTestEntityConfiguration,
92
+ privacyPolicyClass: SimpleTestEntityPrivacyPolicy,
93
+ };
94
+ }
95
+ }
@@ -0,0 +1,130 @@
1
+ import {
2
+ AlwaysAllowPrivacyPolicyRule,
3
+ DateField,
4
+ Entity,
5
+ EntityCompanionDefinition,
6
+ EntityConfiguration,
7
+ EntityPrivacyPolicy,
8
+ IntField,
9
+ StringField,
10
+ UUIDField,
11
+ ViewerContext,
12
+ } from '@expo/entity';
13
+ import { result, Result } from '@expo/results';
14
+
15
+ export type TestFields = {
16
+ customIdField: string;
17
+ testIndexedField: string;
18
+ stringField: string;
19
+ intField: number;
20
+ dateField: Date;
21
+ nullableField: string | null;
22
+ };
23
+
24
+ export const testEntityConfiguration = new EntityConfiguration<TestFields, 'customIdField'>({
25
+ idField: 'customIdField',
26
+ tableName: 'test_entity_should_not_write_to_db',
27
+ schema: {
28
+ customIdField: new UUIDField({
29
+ columnName: 'custom_id',
30
+ cache: true,
31
+ }),
32
+ testIndexedField: new StringField({
33
+ columnName: 'test_index',
34
+ cache: true,
35
+ }),
36
+ stringField: new StringField({
37
+ columnName: 'string_field',
38
+ }),
39
+ intField: new IntField({
40
+ columnName: 'number_field',
41
+ }),
42
+ dateField: new DateField({
43
+ columnName: 'date_field',
44
+ }),
45
+ nullableField: new StringField({
46
+ columnName: 'nullable_field',
47
+ }),
48
+ },
49
+ databaseAdapterFlavor: 'postgres',
50
+ cacheAdapterFlavor: 'redis',
51
+ compositeFieldDefinitions: [
52
+ { compositeField: ['stringField', 'intField'], cache: false },
53
+ { compositeField: ['stringField', 'testIndexedField'], cache: true },
54
+ { compositeField: ['nullableField', 'testIndexedField'], cache: true },
55
+ ],
56
+ });
57
+
58
+ export class TestEntityPrivacyPolicy extends EntityPrivacyPolicy<
59
+ TestFields,
60
+ 'customIdField',
61
+ ViewerContext,
62
+ TestEntity
63
+ > {
64
+ protected override readonly readRules = [
65
+ new AlwaysAllowPrivacyPolicyRule<TestFields, 'customIdField', ViewerContext, TestEntity>(),
66
+ ];
67
+ protected override readonly createRules = [
68
+ new AlwaysAllowPrivacyPolicyRule<TestFields, 'customIdField', ViewerContext, TestEntity>(),
69
+ ];
70
+ protected override readonly updateRules = [
71
+ new AlwaysAllowPrivacyPolicyRule<TestFields, 'customIdField', ViewerContext, TestEntity>(),
72
+ ];
73
+ protected override readonly deleteRules = [
74
+ new AlwaysAllowPrivacyPolicyRule<TestFields, 'customIdField', ViewerContext, TestEntity>(),
75
+ ];
76
+ }
77
+
78
+ export class TestEntity extends Entity<TestFields, 'customIdField', ViewerContext> {
79
+ static defineCompanionDefinition(): EntityCompanionDefinition<
80
+ TestFields,
81
+ 'customIdField',
82
+ ViewerContext,
83
+ TestEntity,
84
+ TestEntityPrivacyPolicy
85
+ > {
86
+ return {
87
+ entityClass: TestEntity,
88
+ entityConfiguration: testEntityConfiguration,
89
+ privacyPolicyClass: TestEntityPrivacyPolicy,
90
+ };
91
+ }
92
+
93
+ getBlah(): string {
94
+ return 'Hello World!';
95
+ }
96
+
97
+ static async helloAsync(
98
+ viewerContext: ViewerContext,
99
+ testValue: string,
100
+ ): Promise<Result<TestEntity>> {
101
+ const fields = {
102
+ customIdField: testValue,
103
+ testIndexedField: 'hello',
104
+ stringField: 'hello',
105
+ intField: 1,
106
+ dateField: new Date(),
107
+ nullableField: null,
108
+ };
109
+ return result(
110
+ new TestEntity({
111
+ viewerContext,
112
+ id: testValue,
113
+ databaseFields: fields,
114
+ selectedFields: fields,
115
+ }),
116
+ );
117
+ }
118
+
119
+ static async returnErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
120
+ return result(new Error('return entity'));
121
+ }
122
+
123
+ static async throwErrorAsync(_viewerContext: ViewerContext): Promise<Result<TestEntity>> {
124
+ throw new Error('threw entity');
125
+ }
126
+
127
+ static async nonResultAsync(_viewerContext: ViewerContext, testValue: string): Promise<string> {
128
+ return testValue;
129
+ }
130
+ }
@@ -0,0 +1,62 @@
1
+ import {
2
+ AlwaysAllowPrivacyPolicyRule,
3
+ Entity,
4
+ EntityCompanionDefinition,
5
+ EntityConfiguration,
6
+ EntityPrivacyPolicy,
7
+ IntField,
8
+ ViewerContext,
9
+ } from '@expo/entity';
10
+
11
+ export type NumberKeyFields = {
12
+ id: number;
13
+ };
14
+
15
+ export const numberKeyEntityConfiguration = new EntityConfiguration<NumberKeyFields, 'id'>({
16
+ idField: 'id',
17
+ tableName: 'simple_test_entity_should_not_write_to_db',
18
+ schema: {
19
+ id: new IntField({
20
+ columnName: 'custom_id',
21
+ cache: false,
22
+ }),
23
+ },
24
+ databaseAdapterFlavor: 'postgres',
25
+ cacheAdapterFlavor: 'redis',
26
+ });
27
+
28
+ export class NumberKeyPrivacyPolicy extends EntityPrivacyPolicy<
29
+ NumberKeyFields,
30
+ 'id',
31
+ ViewerContext,
32
+ NumberKeyEntity
33
+ > {
34
+ protected override readonly readRules = [
35
+ new AlwaysAllowPrivacyPolicyRule<NumberKeyFields, 'id', ViewerContext, NumberKeyEntity>(),
36
+ ];
37
+ protected override readonly createRules = [
38
+ new AlwaysAllowPrivacyPolicyRule<NumberKeyFields, 'id', ViewerContext, NumberKeyEntity>(),
39
+ ];
40
+ protected override readonly updateRules = [
41
+ new AlwaysAllowPrivacyPolicyRule<NumberKeyFields, 'id', ViewerContext, NumberKeyEntity>(),
42
+ ];
43
+ protected override readonly deleteRules = [
44
+ new AlwaysAllowPrivacyPolicyRule<NumberKeyFields, 'id', ViewerContext, NumberKeyEntity>(),
45
+ ];
46
+ }
47
+
48
+ export class NumberKeyEntity extends Entity<NumberKeyFields, 'id', ViewerContext> {
49
+ static defineCompanionDefinition(): EntityCompanionDefinition<
50
+ NumberKeyFields,
51
+ 'id',
52
+ ViewerContext,
53
+ NumberKeyEntity,
54
+ NumberKeyPrivacyPolicy
55
+ > {
56
+ return {
57
+ entityClass: NumberKeyEntity,
58
+ entityConfiguration: numberKeyEntityConfiguration,
59
+ privacyPolicyClass: NumberKeyPrivacyPolicy,
60
+ };
61
+ }
62
+ }
@@ -0,0 +1,33 @@
1
+ import { expect, it } from '@jest/globals';
2
+ import { readFile } from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ it.each([
6
+ 'StubPostgresDatabaseAdapter',
7
+ 'StubPostgresDatabaseAdapterProvider',
8
+ 'createUnitTestPostgresEntityCompanionProvider',
9
+ ])('%s is the same as in @expo/entity-database-adapter-knex', async (fileName) => {
10
+ // These stub adapters need to be shared for testing, but we can't have them in the main
11
+ // entity-database-adapter-knex package since they would be exposed in production.
12
+ // Therefore, we duplicate them and ensure they stay in sync.
13
+
14
+ const fileContentsFromEntityDatabaseAdapterKnex = await readFile(
15
+ path.resolve(
16
+ __dirname,
17
+ `../../../entity-database-adapter-knex/src/__tests__/fixtures/${fileName}.ts`,
18
+ ),
19
+ 'utf-8',
20
+ );
21
+ const fileContentsFromTestingUtils = await readFile(
22
+ path.resolve(__dirname, `../${fileName}.ts`),
23
+ 'utf-8',
24
+ );
25
+
26
+ const trimmedFiles = [
27
+ fileContentsFromEntityDatabaseAdapterKnex,
28
+ fileContentsFromTestingUtils,
29
+ ].map((file) => file.substring(file.indexOf('export')));
30
+
31
+ expect(trimmedFiles[0]?.length).toBeGreaterThan(0);
32
+ expect(trimmedFiles[0]).toEqual(trimmedFiles[1]);
33
+ });