mirror of
https://github.com/expo/entity.git
synced 2026-11-15 03:48:08 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
448d75b402 |
No files matched your search
@@ -20,12 +20,18 @@
|
||||
{
|
||||
"project": "packages/entity-cache-adapter-redis/tsconfig.json",
|
||||
"output": "packages/entity-cache-adapter-redis/src",
|
||||
"exclude": [ "**/__testfixtures__/**" ]
|
||||
"exclude": [
|
||||
"**/__testfixtures__/**",
|
||||
"**/__integration-tests__/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "packages/entity-database-adapter-knex/tsconfig.json",
|
||||
"output": "packages/entity-database-adapter-knex/src",
|
||||
"exclude": [ "**/__testfixtures__/**" ]
|
||||
"exclude": [
|
||||
"**/__testfixtures__/**",
|
||||
"**/__integration-tests__/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"project": "packages/entity-ip-address-field/tsconfig.json",
|
||||
|
||||
@@ -20,17 +20,10 @@ jobs:
|
||||
- run: yarn lint --max-warnings=0
|
||||
- name: Check if barrels are up to date
|
||||
run: yarn ctix && [ -z "$(git status --porcelain)" ]
|
||||
- run: yarn test --coverage
|
||||
- run: yarn integration --coverage
|
||||
- uses: SimenB/github-actions-cpu-cores@v2
|
||||
id: cpu-cores
|
||||
- run: yarn test:all ---maxWorkers ${{ steps.cpu-cores.outputs.count }}
|
||||
- uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
directory: ./coverage
|
||||
flags: unittest
|
||||
fail_ci_if_error: true
|
||||
- uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
directory: ./coverage-integration
|
||||
flags: integration
|
||||
fail_ci_if_error: true
|
||||
+1
-6
@@ -38,12 +38,7 @@ node_modules/
|
||||
!**/.yarn/versions
|
||||
**/.pnp.*
|
||||
|
||||
.nx/cache
|
||||
.nx/workspace-data
|
||||
|
||||
# Entity-specific ignores
|
||||
|
||||
build/
|
||||
coverage/
|
||||
coverage-integration/
|
||||
doc/
|
||||
doc/
|
||||
@@ -1,7 +0,0 @@
|
||||
const unitConfig = require('./jest.config.js');
|
||||
|
||||
module.exports = {
|
||||
...unitConfig,
|
||||
coverageDirectory: 'coverage-integration',
|
||||
testMatch: ['**/__integration-tests__/**/*-test.ts'],
|
||||
};
|
||||
+12
-1
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
transform: { '\\.[jt]sx?$': ['babel-jest', { rootMode: 'upward' }] },
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
'packages/*/src/**',
|
||||
'!packages/*/src/index.ts',
|
||||
@@ -9,5 +10,15 @@ module.exports = {
|
||||
'!**/__*test*__/**',
|
||||
],
|
||||
coverageProvider: 'v8',
|
||||
testMatch: ['**/__tests__/**/*-test.ts'],
|
||||
randomize: true,
|
||||
testEnvironmentOptions: { globalsCleanupMode: 'on' },
|
||||
workerThreads: true,
|
||||
projects: [
|
||||
{ displayName: 'unit', testMatch: ['**/__tests__/**/*-test.ts'] },
|
||||
{
|
||||
displayName: 'integration',
|
||||
globalSetup: '<rootDir>/setup.mjs',
|
||||
testMatch: ['**/__integration-tests__/**/*-test.ts'],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"targetDefaults": {
|
||||
"tsc": {
|
||||
"dependsOn": ["^clean", "clean", "^tsc"]
|
||||
},
|
||||
"build": {
|
||||
"dependsOn": ["^clean", "clean", "^build"]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["^tsc"]
|
||||
},
|
||||
"integration": {
|
||||
"dependsOn": ["^tsc"]
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -10,12 +10,13 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"test": "yarn build --noCheck && jest",
|
||||
"test:all": "yarn test",
|
||||
"integration": "./resources/run-with-docker yarn test --config jest-integration.config.js --runInBand",
|
||||
"test": "yarn test:unit",
|
||||
"test:unit": "yarn test:all --selectProjects unit",
|
||||
"test:all": "yarn build --noCheck && DEBUG=testcontainers:pull jest",
|
||||
"integration": "yarn test:all --selectProjects integration",
|
||||
"integration:all": "yarn integration",
|
||||
"prepack": "yarn build",
|
||||
"ctix": "ctix build && resources/prepend-barrels.sh",
|
||||
"ctix": "ctix build && ./prepend-barrels.sh",
|
||||
"typedoc": "yarn build && typedoc"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,6 +36,7 @@
|
||||
"lerna": "^8.2.2",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"testcontainers": "^11.0.3",
|
||||
"typedoc": "^0.28.4",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"test": "yarn test:all --rootDir $(pwd)",
|
||||
"integration": "yarn integration:all --rootDir $(pwd)"
|
||||
"test": "yarn test:unit --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"test": "yarn test:all --rootDir $(pwd)",
|
||||
"integration": "yarn integration:all --rootDir $(pwd)"
|
||||
"test": "yarn test:unit --roots $(pwd)",
|
||||
"integration": "yarn test:integration --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
@@ -34,6 +34,7 @@
|
||||
"@expo/batcher": "^1.0.0",
|
||||
"@expo/entity-testing-utils": "workspace:^",
|
||||
"@jest/globals": "^30.0.0",
|
||||
"@testcontainers/redis": "^11.0.3",
|
||||
"ioredis": "^5.6.0",
|
||||
"ts-mockito": "^2.6.1",
|
||||
"typescript": "^5.8.3"
|
||||
|
||||
+15
-15
@@ -8,9 +8,7 @@ import {
|
||||
} from '@expo/entity';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
import invariant from 'invariant';
|
||||
import Redis from 'ioredis';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { URL } from 'url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import {
|
||||
@@ -20,6 +18,7 @@ import {
|
||||
IRedisTransaction,
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '../GenericRedisCacher';
|
||||
import { Redis, StartedRedisContainer, startRedisAsync } from './testcontainer';
|
||||
import { RedisTestEntity, RedisTestEntityFields } from '../__testfixtures__/RedisTestEntity';
|
||||
import { createRedisIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createRedisIntegrationTestEntityCompanionProvider';
|
||||
|
||||
@@ -69,14 +68,14 @@ class BatchedRedis implements IRedis {
|
||||
}
|
||||
|
||||
describe(GenericRedisCacher, () => {
|
||||
const redis = new Redis(new URL(process.env['REDIS_URL']!).toString());
|
||||
const redisClient = new BatchedRedis(redis);
|
||||
|
||||
let container: StartedRedisContainer;
|
||||
let redisClient: Redis;
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
({ container, redisClient } = await startRedisAsync());
|
||||
genericRedisCacheContext = {
|
||||
redisClient,
|
||||
redisClient: new BatchedRedis(redisClient),
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
const delimiter = ':';
|
||||
const escapedParts = parts.map((part) =>
|
||||
@@ -94,11 +93,12 @@ describe(GenericRedisCacher, () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await redis.flushdb();
|
||||
await redisClient.flushdb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
redis.disconnect();
|
||||
redisClient.disconnect();
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
it('has correct caching behavior', async () => {
|
||||
@@ -107,7 +107,7 @@ describe(GenericRedisCacher, () => {
|
||||
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
|
||||
);
|
||||
|
||||
const mgetSpy = jest.spyOn(redis, 'mget');
|
||||
const mgetSpy = jest.spyOn(redisClient, 'mget');
|
||||
|
||||
const genericCacher = viewerContext.entityCompanionProvider.getCompanionForEntity(
|
||||
RedisTestEntity,
|
||||
@@ -149,7 +149,7 @@ describe(GenericRedisCacher, () => {
|
||||
new SingleFieldHolder('id'),
|
||||
new SingleFieldValueHolder(entity1Created.getID()),
|
||||
);
|
||||
const cachedJSON = await redis.get(cacheKeyEntity1);
|
||||
const cachedJSON = await redisClient.get(cacheKeyEntity1);
|
||||
const cachedValue = JSON.parse(cachedJSON!);
|
||||
expect(cachedValue).toMatchObject({
|
||||
id: entity1.getID(),
|
||||
@@ -164,7 +164,7 @@ describe(GenericRedisCacher, () => {
|
||||
'name',
|
||||
entity1Created.getField('name'),
|
||||
);
|
||||
await expect(redis.get(cacheKeyEntity1NameField)).resolves.toEqual(cachedJSON);
|
||||
await expect(redisClient.get(cacheKeyEntity1NameField)).resolves.toEqual(cachedJSON);
|
||||
|
||||
// simulate non existent db fetch, should write negative result ('') to cache
|
||||
const nonExistentId = uuidv4();
|
||||
@@ -177,7 +177,7 @@ describe(GenericRedisCacher, () => {
|
||||
new SingleFieldHolder('id'),
|
||||
new SingleFieldValueHolder(nonExistentId),
|
||||
);
|
||||
const nonExistentCachedValue = await redis.get(cacheKeyNonExistent);
|
||||
const nonExistentCachedValue = await redisClient.get(cacheKeyNonExistent);
|
||||
expect(nonExistentCachedValue).toEqual('');
|
||||
// load again through entities framework to ensure it reads negative result
|
||||
const entityNonExistentResult2 =
|
||||
@@ -188,7 +188,7 @@ describe(GenericRedisCacher, () => {
|
||||
|
||||
// invalidate from cache to ensure it invalidates correctly in both caches
|
||||
await RedisTestEntity.loaderUtils(viewerContext).invalidateFieldsAsync(entity1.getAllFields());
|
||||
await expect(redis.get(cacheKeyEntity1)).resolves.toBeNull();
|
||||
await expect(redis.get(cacheKeyEntity1NameField)).resolves.toBeNull();
|
||||
await expect(redisClient.get(cacheKeyEntity1)).resolves.toBeNull();
|
||||
await expect(redisClient.get(cacheKeyEntity1NameField)).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
+10
-6
@@ -8,8 +8,6 @@ import {
|
||||
} from '@expo/entity';
|
||||
import { enforceAsyncResult } from '@expo/results';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import Redis from 'ioredis';
|
||||
import { URL } from 'url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import {
|
||||
@@ -17,17 +15,21 @@ import {
|
||||
GenericRedisCacher,
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '../GenericRedisCacher';
|
||||
import { Redis, StartedRedisContainer, startRedisAsync } from './testcontainer';
|
||||
import { RedisTestEntity, RedisTestEntityFields } from '../__testfixtures__/RedisTestEntity';
|
||||
import { createRedisIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createRedisIntegrationTestEntityCompanionProvider';
|
||||
|
||||
class TestViewerContext extends ViewerContext {}
|
||||
|
||||
describe(GenericRedisCacher, () => {
|
||||
let container: StartedRedisContainer;
|
||||
let redisClient: Redis;
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
({ container, redisClient } = await startRedisAsync());
|
||||
genericRedisCacheContext = {
|
||||
redisClient: new Redis(new URL(process.env['REDIS_URL']!).toString()),
|
||||
redisClient,
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
const delimiter = ':';
|
||||
const escapedParts = parts.map((part) =>
|
||||
@@ -45,10 +47,12 @@ describe(GenericRedisCacher, () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await (genericRedisCacheContext.redisClient as Redis).flushdb();
|
||||
await redisClient.flushdb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
(genericRedisCacheContext.redisClient as Redis).disconnect();
|
||||
redisClient.disconnect();
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
it('has correct caching behavior', async () => {
|
||||
|
||||
+10
-6
@@ -1,13 +1,12 @@
|
||||
import { CacheStatus, ViewerContext } from '@expo/entity';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import Redis from 'ioredis';
|
||||
import { URL } from 'url';
|
||||
|
||||
import {
|
||||
GenericRedisCacheContext,
|
||||
GenericRedisCacher,
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '../GenericRedisCacher';
|
||||
import { Redis, StartedRedisContainer, startRedisAsync } from './testcontainer';
|
||||
import {
|
||||
RedisTestEntity,
|
||||
redisTestEntityConfiguration,
|
||||
@@ -19,10 +18,13 @@ class TestViewerContext extends ViewerContext {}
|
||||
|
||||
describe(GenericRedisCacher, () => {
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
let redisClient: Redis;
|
||||
let container: StartedRedisContainer;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
({ redisClient, container } = await startRedisAsync());
|
||||
genericRedisCacheContext = {
|
||||
redisClient: new Redis(new URL(process.env['REDIS_URL']!).toString()),
|
||||
redisClient,
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
const delimiter = ':';
|
||||
const escapedParts = parts.map((part) =>
|
||||
@@ -40,10 +42,12 @@ describe(GenericRedisCacher, () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await (genericRedisCacheContext.redisClient as Redis).flushdb();
|
||||
await redisClient.flushdb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
(genericRedisCacheContext.redisClient as Redis).disconnect();
|
||||
redisClient.disconnect();
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
it('has correct caching and loading behavior', async () => {
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { EntityCacheAdapterTransientError, ViewerContext } from '@expo/entity';
|
||||
import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import Redis from 'ioredis';
|
||||
import { URL } from 'url';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
|
||||
import {
|
||||
GenericRedisCacheContext,
|
||||
GenericRedisCacher,
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '../GenericRedisCacher';
|
||||
import { Redis, StartedRedisContainer, startRedisAsync } from './testcontainer';
|
||||
import { RedisTestEntity } from '../__testfixtures__/RedisTestEntity';
|
||||
import { createRedisIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createRedisIntegrationTestEntityCompanionProvider';
|
||||
|
||||
class TestViewerContext extends ViewerContext {}
|
||||
|
||||
describe(GenericRedisCacher, () => {
|
||||
const redisClient = new Redis(new URL(process.env['REDIS_URL']!).toString());
|
||||
let container: StartedRedisContainer;
|
||||
let redisClient: Redis;
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
({ container, redisClient } = await startRedisAsync());
|
||||
genericRedisCacheContext = {
|
||||
redisClient,
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
@@ -40,6 +41,10 @@ describe(GenericRedisCacher, () => {
|
||||
await redisClient.flushdb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
it('throws when redis is disconnected', async () => {
|
||||
redisClient.disconnect();
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { RedisContainer, type StartedRedisContainer } from '@testcontainers/redis';
|
||||
import { Redis } from 'ioredis';
|
||||
|
||||
export type { StartedRedisContainer } from '@testcontainers/redis';
|
||||
export type { Redis } from 'ioredis';
|
||||
|
||||
export async function startRedisAsync(): Promise<{
|
||||
redisClient: Redis;
|
||||
container: StartedRedisContainer;
|
||||
}> {
|
||||
const container = await new RedisContainer('redis').start();
|
||||
const redisClient = new Redis(container.getConnectionUrl());
|
||||
return { redisClient, container };
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"test": "yarn test:all --rootDir $(pwd)"
|
||||
"test": "yarn test:unit --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"test": "yarn test:all --rootDir $(pwd)",
|
||||
"integration": "yarn integration:all --rootDir $(pwd)"
|
||||
"test": "yarn test:unit --roots $(pwd)",
|
||||
"integration": "yarn test:integration --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
@@ -31,6 +31,7 @@
|
||||
"devDependencies": {
|
||||
"@expo/entity-testing-utils": "workspace:^",
|
||||
"@jest/globals": "^30.0.0",
|
||||
"@testcontainers/postgresql": "^11.0.3",
|
||||
"pg": "8.14.1",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
+5
-13
@@ -1,25 +1,17 @@
|
||||
import { createWithUniqueConstraintRecoveryAsync, ViewerContext } from '@expo/entity';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import knex, { Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { type Knex, type StartedPostgreSqlContainer, startPostgresAsync } from './testcontainer';
|
||||
import { PostgresUniqueTestEntity } from '../__testfixtures__/PostgresUniqueTestEntity';
|
||||
import { createKnexIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createKnexIntegrationTestEntityCompanionProvider';
|
||||
|
||||
describe(createWithUniqueConstraintRecoveryAsync, () => {
|
||||
let container: StartedPostgreSqlContainer;
|
||||
let knexInstance: Knex;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
beforeAll(async () => {
|
||||
({ container, knexInstance } = await startPostgresAsync());
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -27,8 +19,8 @@ describe(createWithUniqueConstraintRecoveryAsync, () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await PostgresUniqueTestEntity.dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
describe.each([true, false])('is parallel creations %p', (parallel) => {
|
||||
|
||||
+5
-14
@@ -2,29 +2,20 @@ import { OrderByOrdering, TransactionIsolationLevel, ViewerContext } from '@expo
|
||||
import { createUnitTestEntityCompanionProvider } from '@expo/entity-testing-utils';
|
||||
import { enforceAsyncResult } from '@expo/results';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, test } from '@jest/globals';
|
||||
import { knex, Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
|
||||
import { type Knex, type StartedPostgreSqlContainer, startPostgresAsync } from './testcontainer';
|
||||
import { PostgresTestEntity } from '../__testfixtures__/PostgresTestEntity';
|
||||
import { PostgresTriggerTestEntity } from '../__testfixtures__/PostgresTriggerTestEntity';
|
||||
import { PostgresValidatorTestEntity } from '../__testfixtures__/PostgresValidatorTestEntity';
|
||||
import { createKnexIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createKnexIntegrationTestEntityCompanionProvider';
|
||||
|
||||
describe('postgres entity integration', () => {
|
||||
let container: StartedPostgreSqlContainer;
|
||||
let knexInstance: Knex;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
beforeAll(async () => {
|
||||
({ container, knexInstance } = await startPostgresAsync());
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -32,8 +23,8 @@ describe('postgres entity integration', () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await PostgresTestEntity.dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
it('supports parallel partial updates', async () => {
|
||||
|
||||
+5
-14
@@ -1,26 +1,17 @@
|
||||
import { TransactionalDataLoaderMode, ViewerContext } from '@expo/entity';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, test } from '@jest/globals';
|
||||
import { knex, Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { PostgresEntityQueryContextProvider } from '../PostgresEntityQueryContextProvider';
|
||||
import { type Knex, type StartedPostgreSqlContainer, startPostgresAsync } from './testcontainer';
|
||||
import { PostgresUniqueTestEntity } from '../__testfixtures__/PostgresUniqueTestEntity';
|
||||
import { createKnexIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createKnexIntegrationTestEntityCompanionProvider';
|
||||
|
||||
describe(PostgresEntityQueryContextProvider, () => {
|
||||
let container: StartedPostgreSqlContainer;
|
||||
let knexInstance: Knex;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
beforeAll(async () => {
|
||||
({ container, knexInstance } = await startPostgresAsync());
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -28,8 +19,8 @@ describe(PostgresEntityQueryContextProvider, () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await PostgresUniqueTestEntity.dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
it('supports nested transactions', async () => {
|
||||
|
||||
+5
-14
@@ -1,26 +1,17 @@
|
||||
import { ViewerContext } from '@expo/entity';
|
||||
import { enforceAsyncResult } from '@expo/results';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import { knex, Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import { type Knex, type StartedPostgreSqlContainer, startPostgresAsync } from './testcontainer';
|
||||
import { InvalidTestEntity } from '../__testfixtures__/InvalidTestEntity';
|
||||
import { createKnexIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createKnexIntegrationTestEntityCompanionProvider';
|
||||
|
||||
describe('postgres entity integration', () => {
|
||||
let container: StartedPostgreSqlContainer;
|
||||
let knexInstance: Knex;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
beforeAll(async () => {
|
||||
({ container, knexInstance } = await startPostgresAsync());
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -28,8 +19,8 @@ describe('postgres entity integration', () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await InvalidTestEntity.dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
describe('invalid operation result counts', () => {
|
||||
|
||||
@@ -9,26 +9,22 @@ import {
|
||||
ViewerContext,
|
||||
} from '@expo/entity';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import { knex, Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
import {
|
||||
type Knex,
|
||||
type StartedPostgreSqlContainer,
|
||||
containerKnex,
|
||||
startPostgresAsync,
|
||||
} from './testcontainer';
|
||||
import { ErrorsTestEntity } from '../__testfixtures__/ErrorsTestEntity';
|
||||
import { createKnexIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createKnexIntegrationTestEntityCompanionProvider';
|
||||
|
||||
describe('postgres errors', () => {
|
||||
let container: StartedPostgreSqlContainer;
|
||||
let knexInstance: Knex;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
beforeAll(async () => {
|
||||
({ container, knexInstance } = await startPostgresAsync());
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -36,8 +32,8 @@ describe('postgres errors', () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await ErrorsTestEntity.dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
it('throws EntityDatabaseAdapterTransientError on Knex timeout', async () => {
|
||||
@@ -47,15 +43,7 @@ describe('postgres errors', () => {
|
||||
.setField('fieldNonNull', 'hello')
|
||||
.createAsync();
|
||||
|
||||
const shortTimeoutKnexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
const shortTimeoutKnexInstance = containerKnex(container, {
|
||||
acquireConnectionTimeout: 1,
|
||||
});
|
||||
const vc2 = new ViewerContext(
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
||||
import { knex, Knex } from 'knex';
|
||||
|
||||
export type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
||||
export type { Knex } from 'knex';
|
||||
|
||||
export function containerKnex(container: StartedPostgreSqlContainer, options?: Knex.Config): Knex {
|
||||
return knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
connectionString: container.getConnectionUri(),
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export async function startPostgresAsync(): Promise<{
|
||||
knexInstance: Knex;
|
||||
container: StartedPostgreSqlContainer;
|
||||
}> {
|
||||
const container = await new PostgreSqlContainer('postgres:14').start();
|
||||
const knexInstance = containerKnex(container);
|
||||
return { container, knexInstance };
|
||||
}
|
||||
@@ -9,8 +9,7 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"test": "yarn test:all --rootDir $(pwd)",
|
||||
"integration": "yarn integration:all --rootDir $(pwd)",
|
||||
"test": "yarn test:unit --roots $(pwd)",
|
||||
"start": "yarn build --noCheck && node --watch --experimental-transform-types --inspect --async-stack-traces --throw-deprecation src/index.ts"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"integration": "yarn integration:all --rootDir $(pwd)"
|
||||
"integration": "yarn test:integration --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
@@ -25,6 +25,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^30.0.0",
|
||||
"@testcontainers/postgresql": "^11.0.3",
|
||||
"@testcontainers/redis": "^11.0.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
+16
-25
@@ -1,23 +1,26 @@
|
||||
import {
|
||||
EntityPrivacyPolicy,
|
||||
ViewerContext,
|
||||
AlwaysAllowPrivacyPolicyRule,
|
||||
Entity,
|
||||
EntityCompanionDefinition,
|
||||
EntityConfiguration,
|
||||
EntityPrivacyPolicy,
|
||||
StringField,
|
||||
UUIDField,
|
||||
ViewerContext,
|
||||
} from '@expo/entity';
|
||||
import {
|
||||
GenericRedisCacheContext,
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '@expo/entity-cache-adapter-redis';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, test } from '@jest/globals';
|
||||
import Redis from 'ioredis';
|
||||
import { knex, Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { URL } from 'url';
|
||||
|
||||
import {
|
||||
type Knex,
|
||||
type Redis,
|
||||
type StartedPostgreSqlContainer,
|
||||
type StartedRedisContainer,
|
||||
startServicesAsync,
|
||||
} from './testcontainer';
|
||||
import { createFullIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createFullIntegrationTestEntityCompanionProvider';
|
||||
|
||||
interface TestFields {
|
||||
@@ -91,28 +94,15 @@ async function createOrTruncatePostgresTablesAsync(knex: Knex): Promise<void> {
|
||||
await knex.into('testentities').truncate();
|
||||
}
|
||||
|
||||
async function dropPostgresTableAsync(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable('testentities')) {
|
||||
await knex.schema.dropTable('testentities');
|
||||
}
|
||||
}
|
||||
|
||||
describe('Entity cache inconsistency', () => {
|
||||
let postgresContainer: StartedPostgreSqlContainer;
|
||||
let redisContainer: StartedRedisContainer;
|
||||
let knexInstance: Knex;
|
||||
const redisClient = new Redis(new URL(process.env['REDIS_URL']!).toString());
|
||||
let redisClient: Redis;
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
beforeAll(async () => {
|
||||
({ knexInstance, redisClient, postgresContainer, redisContainer } = await startServicesAsync());
|
||||
genericRedisCacheContext = {
|
||||
redisClient,
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
@@ -137,9 +127,10 @@ describe('Entity cache inconsistency', () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
redisClient.disconnect();
|
||||
await postgresContainer.stop();
|
||||
await redisContainer.stop();
|
||||
});
|
||||
|
||||
test('lots of updates in long-ish running transactions', async () => {
|
||||
|
||||
+29
-75
@@ -1,23 +1,26 @@
|
||||
import {
|
||||
EntityPrivacyPolicy,
|
||||
ViewerContext,
|
||||
AlwaysAllowPrivacyPolicyRule,
|
||||
Entity,
|
||||
EntityCompanionDefinition,
|
||||
EntityConfiguration,
|
||||
EntityPrivacyPolicy,
|
||||
StringField,
|
||||
UUIDField,
|
||||
ViewerContext,
|
||||
} from '@expo/entity';
|
||||
import {
|
||||
GenericRedisCacheContext,
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '@expo/entity-cache-adapter-redis';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import Redis from 'ioredis';
|
||||
import { knex, Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { URL } from 'url';
|
||||
import { afterEach, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
|
||||
import {
|
||||
type Knex,
|
||||
type Redis,
|
||||
type StartedPostgreSqlContainer,
|
||||
type StartedRedisContainer,
|
||||
startServicesAsync,
|
||||
} from './testcontainer';
|
||||
import { createFullIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createFullIntegrationTestEntityCompanionProvider';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
@@ -91,29 +94,25 @@ async function createOrTruncatePostgresTablesAsync(knex: Knex): Promise<void> {
|
||||
await knex.into('testentities').truncate();
|
||||
}
|
||||
|
||||
async function dropPostgresTableAsync(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable('testentities')) {
|
||||
await knex.schema.dropTable('testentities');
|
||||
}
|
||||
}
|
||||
|
||||
describe('Lack of entity cache push safety with RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION', () => {
|
||||
describe('Entity cache push safety', () => {
|
||||
let postgresContainer: StartedPostgreSqlContainer;
|
||||
let redisContainer: StartedRedisContainer;
|
||||
let knexInstance: Knex;
|
||||
const redisClient = new Redis(new URL(process.env['REDIS_URL']!).toString());
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
let redisClient: Redis;
|
||||
beforeEach(async () => {
|
||||
({ knexInstance, redisClient, postgresContainer, redisContainer } = await startServicesAsync());
|
||||
await createOrTruncatePostgresTablesAsync(knexInstance);
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
genericRedisCacheContext = {
|
||||
afterEach(async () => {
|
||||
await knexInstance.destroy();
|
||||
redisClient.disconnect();
|
||||
await postgresContainer.stop();
|
||||
await redisContainer.stop();
|
||||
});
|
||||
|
||||
it('is absent with RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION', async () => {
|
||||
const genericRedisCacheContext: GenericRedisCacheContext = {
|
||||
redisClient,
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
const delimiter = ':';
|
||||
@@ -129,20 +128,6 @@ describe('Lack of entity cache push safety with RedisCacheInvalidationStrategy.C
|
||||
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await createOrTruncatePostgresTablesAsync(knexInstance);
|
||||
await redisClient.flushdb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
redisClient.disconnect();
|
||||
});
|
||||
|
||||
it('does not cross-invalidates cache and thus has cache inconsistency', async () => {
|
||||
const preDeployCacheKeyVersion = 1;
|
||||
const newCacheKeyVersion = 2;
|
||||
|
||||
@@ -187,25 +172,8 @@ describe('Lack of entity cache push safety with RedisCacheInvalidationStrategy.C
|
||||
// in a consistent system this would be req2. This fails due to lack of cross-invalidation since the cache for pre-deploy is stale and wasn't invalidated.
|
||||
expect(entityReq3.getField('string_field')).toBe('req1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Entity cache push safety with RedisCacheInvalidationStrategy.SURROUNDING_CACHE_KEY_VERSIONS', () => {
|
||||
let knexInstance: Knex;
|
||||
const redisClient = new Redis(new URL(process.env['REDIS_URL']!).toString());
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
genericRedisCacheContext = {
|
||||
it('is present with RedisCacheInvalidationStrategy.SURROUNDING_CACHE_KEY_VERSION', async () => {
|
||||
const genericRedisCacheContext: GenericRedisCacheContext = {
|
||||
redisClient,
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
const delimiter = ':';
|
||||
@@ -221,20 +189,6 @@ describe('Entity cache push safety with RedisCacheInvalidationStrategy.SURROUNDI
|
||||
invalidationStrategy: RedisCacheInvalidationStrategy.SURROUNDING_CACHE_KEY_VERSIONS,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await createOrTruncatePostgresTablesAsync(knexInstance);
|
||||
await redisClient.flushdb();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
redisClient.disconnect();
|
||||
});
|
||||
|
||||
it('correctly cross-invalidates cache with new and old code (cache key) versions', async () => {
|
||||
const preDeployCacheKeyVersion = 1;
|
||||
const newCacheKeyVersion = 2;
|
||||
|
||||
|
||||
+15
-17
@@ -4,14 +4,17 @@ import {
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '@expo/entity-cache-adapter-redis';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import Redis from 'ioredis';
|
||||
import { knex, Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { createFullIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createFullIntegrationTestEntityCompanionProvider';
|
||||
import ChildEntity from './entities/ChildEntity';
|
||||
import ParentEntity from './entities/ParentEntity';
|
||||
import { createFullIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createFullIntegrationTestEntityCompanionProvider';
|
||||
import {
|
||||
type Knex,
|
||||
type Redis,
|
||||
type StartedPostgreSqlContainer,
|
||||
type StartedRedisContainer,
|
||||
startServicesAsync,
|
||||
} from './testcontainer';
|
||||
|
||||
async function createOrTruncatePostgresTablesAsync(knex: Knex): Promise<void> {
|
||||
await knex.schema.createTable('parents', (table) => {
|
||||
@@ -36,21 +39,14 @@ async function dropPostgresTableAsync(knex: Knex): Promise<void> {
|
||||
}
|
||||
|
||||
describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
let postgresContainer: StartedPostgreSqlContainer;
|
||||
let redisContainer: StartedRedisContainer;
|
||||
let knexInstance: Knex;
|
||||
const redisClient = new Redis(new URL(process.env['REDIS_URL']!).toString());
|
||||
let redisClient: Redis;
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
beforeAll(async () => {
|
||||
({ knexInstance, redisClient, postgresContainer, redisContainer } = await startServicesAsync());
|
||||
genericRedisCacheContext = {
|
||||
redisClient,
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
@@ -78,6 +74,8 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
await dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
redisClient.disconnect();
|
||||
await postgresContainer.stop();
|
||||
await redisContainer.stop();
|
||||
});
|
||||
|
||||
describe('EntityEdgeDeletionBehavior.INVALIDATE_CACHE', () => {
|
||||
|
||||
+16
-18
@@ -1,23 +1,26 @@
|
||||
import {
|
||||
EntityPrivacyPolicy,
|
||||
ViewerContext,
|
||||
AlwaysAllowPrivacyPolicyRule,
|
||||
Entity,
|
||||
EntityCompanionDefinition,
|
||||
EntityConfiguration,
|
||||
EntityPrivacyPolicy,
|
||||
UUIDField,
|
||||
ViewerContext,
|
||||
} from '@expo/entity';
|
||||
import {
|
||||
GenericRedisCacheContext,
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '@expo/entity-cache-adapter-redis';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, test } from '@jest/globals';
|
||||
import Redis from 'ioredis';
|
||||
import { knex, Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { URL } from 'url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import {
|
||||
type Knex,
|
||||
type Redis,
|
||||
type StartedPostgreSqlContainer,
|
||||
type StartedRedisContainer,
|
||||
startServicesAsync,
|
||||
} from './testcontainer';
|
||||
import { createFullIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createFullIntegrationTestEntityCompanionProvider';
|
||||
|
||||
interface TestFields {
|
||||
@@ -87,21 +90,14 @@ async function dropPostgresTableAsync(knex: Knex): Promise<void> {
|
||||
}
|
||||
|
||||
describe('Entity integrity', () => {
|
||||
let postgresContainer: StartedPostgreSqlContainer;
|
||||
let redisContainer: StartedRedisContainer;
|
||||
let knexInstance: Knex;
|
||||
const redisClient = new Redis(new URL(process.env['REDIS_URL']!).toString());
|
||||
let redisClient: Redis;
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
beforeAll(async () => {
|
||||
({ knexInstance, redisClient, postgresContainer, redisContainer } = await startServicesAsync());
|
||||
genericRedisCacheContext = {
|
||||
redisClient,
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
@@ -129,6 +125,8 @@ describe('Entity integrity', () => {
|
||||
await dropPostgresTableAsync(knexInstance);
|
||||
await knexInstance.destroy();
|
||||
redisClient.disconnect();
|
||||
await postgresContainer.stop();
|
||||
await redisContainer.stop();
|
||||
});
|
||||
|
||||
test('cannot update ID', async () => {
|
||||
|
||||
+17
-19
@@ -1,24 +1,27 @@
|
||||
import {
|
||||
EntityPrivacyPolicy,
|
||||
ViewerContext,
|
||||
AlwaysAllowPrivacyPolicyRule,
|
||||
Entity,
|
||||
EntityCompanionDefinition,
|
||||
EntityConfiguration,
|
||||
UUIDField,
|
||||
EntityEdgeDeletionBehavior,
|
||||
EntityPrivacyPolicy,
|
||||
UUIDField,
|
||||
ViewerContext,
|
||||
} from '@expo/entity';
|
||||
import {
|
||||
GenericRedisCacheContext,
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '@expo/entity-cache-adapter-redis';
|
||||
import { afterAll, beforeAll, describe, expect, it } from '@jest/globals';
|
||||
import Redis from 'ioredis';
|
||||
import { knex, Knex } from 'knex';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { URL } from 'url';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import {
|
||||
type Knex,
|
||||
type Redis,
|
||||
type StartedPostgreSqlContainer,
|
||||
type StartedRedisContainer,
|
||||
startServicesAsync,
|
||||
} from './testcontainer';
|
||||
import { createFullIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createFullIntegrationTestEntityCompanionProvider';
|
||||
|
||||
interface CategoryFields {
|
||||
@@ -175,21 +178,14 @@ const makeEntityClasses = async (knex: Knex, edgeDeletionBehavior: EntityEdgeDel
|
||||
return { CategoryEntity, OtherEntity };
|
||||
};
|
||||
describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
let postgresContainer: StartedPostgreSqlContainer;
|
||||
let redisContainer: StartedRedisContainer;
|
||||
let knexInstance: Knex;
|
||||
const redisClient = new Redis(new URL(process.env['REDIS_URL']!).toString());
|
||||
let redisClient: Redis;
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
|
||||
beforeAll(() => {
|
||||
knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
user: nullthrows(process.env['PGUSER']),
|
||||
password: nullthrows(process.env['PGPASSWORD']),
|
||||
host: 'localhost',
|
||||
port: parseInt(nullthrows(process.env['PGPORT']), 10),
|
||||
database: nullthrows(process.env['PGDATABASE']),
|
||||
},
|
||||
});
|
||||
beforeAll(async () => {
|
||||
({ knexInstance, redisClient, postgresContainer, redisContainer } = await startServicesAsync());
|
||||
genericRedisCacheContext = {
|
||||
redisClient,
|
||||
makeKeyFn(...parts: string[]): string {
|
||||
@@ -211,6 +207,8 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
afterAll(async () => {
|
||||
await knexInstance.destroy();
|
||||
redisClient.disconnect();
|
||||
await postgresContainer.stop();
|
||||
await redisContainer.stop();
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { PostgreSqlContainer, type StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
||||
import { RedisContainer, type StartedRedisContainer } from '@testcontainers/redis';
|
||||
import { Redis } from 'ioredis';
|
||||
import { knex, type Knex } from 'knex';
|
||||
|
||||
export type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
||||
export type { StartedRedisContainer } from '@testcontainers/redis';
|
||||
export type { Redis } from 'ioredis';
|
||||
export type { Knex } from 'knex';
|
||||
|
||||
export async function startServicesAsync(): Promise<{
|
||||
knexInstance: Knex<any, unknown[]>;
|
||||
redisClient: Redis;
|
||||
postgresContainer: StartedPostgreSqlContainer;
|
||||
redisContainer: StartedRedisContainer;
|
||||
}> {
|
||||
const postgresContainer = await new PostgreSqlContainer('postgres:14').start();
|
||||
const knexInstance = knex({
|
||||
client: 'pg',
|
||||
connection: {
|
||||
connectionString: postgresContainer.getConnectionUri(),
|
||||
},
|
||||
});
|
||||
const redisContainer = await new RedisContainer('redis').start();
|
||||
const redisClient = new Redis(redisContainer.getConnectionUrl());
|
||||
return { knexInstance, redisClient, postgresContainer, redisContainer };
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn lint --fix",
|
||||
"test": "yarn test:all --rootDir $(pwd)"
|
||||
"test": "yarn test:unit --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
||||
@@ -13,8 +13,7 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn run lint --fix",
|
||||
"test": "yarn test:all --rootDir $(pwd)",
|
||||
"integration": "yarn integration:all --rootDir $(pwd)"
|
||||
"test": "yarn test:unit --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn run lint --fix",
|
||||
"integration": "yarn integration:all --rootDir $(pwd)"
|
||||
"integration": "yarn test:integration --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
||||
+8
-4
@@ -4,9 +4,9 @@ import {
|
||||
RedisCacheInvalidationStrategy,
|
||||
} from '@expo/entity-cache-adapter-redis';
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals';
|
||||
import Redis from 'ioredis';
|
||||
import { RedisContainer, StartedRedisContainer } from '@testcontainers/redis';
|
||||
import { Redis } from 'ioredis';
|
||||
import nullthrows from 'nullthrows';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { RedisSecondaryEntityCache } from '../RedisSecondaryEntityCache';
|
||||
import {
|
||||
@@ -57,10 +57,13 @@ class TestSecondaryRedisCacheLoader extends EntitySecondaryCacheLoader<
|
||||
}
|
||||
|
||||
describe(RedisSecondaryEntityCache, () => {
|
||||
const redisClient = new Redis(new URL(process.env['REDIS_URL']!).toString());
|
||||
let container: StartedRedisContainer;
|
||||
let redisClient: Redis;
|
||||
let genericRedisCacheContext: GenericRedisCacheContext;
|
||||
|
||||
beforeAll(() => {
|
||||
beforeAll(async () => {
|
||||
container = await new RedisContainer('redis').start();
|
||||
redisClient = new Redis(container.getConnectionUrl());
|
||||
genericRedisCacheContext = {
|
||||
redisClient,
|
||||
makeKeyFn(..._parts: string[]): string {
|
||||
@@ -80,6 +83,7 @@ describe(RedisSecondaryEntityCache, () => {
|
||||
});
|
||||
afterAll(async () => {
|
||||
redisClient.disconnect();
|
||||
await container.stop();
|
||||
});
|
||||
|
||||
it('Loads through secondary loader, caches, and invalidates', async () => {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn run lint --fix",
|
||||
"test": "yarn test:all --rootDir $(pwd)"
|
||||
"test": "yarn test:unit --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"clean": "yarn build --clean",
|
||||
"lint": "yarn run --top-level eslint src",
|
||||
"lint-fix": "yarn run lint --fix",
|
||||
"test": "yarn test:all --rootDir $(pwd)"
|
||||
"test": "yarn test:unit --roots $(pwd)"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
|
||||
File renamed without changes.
@@ -1,22 +0,0 @@
|
||||
version: '2'
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- "${REDIS_PORT}:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
postgres:
|
||||
image: postgres:14
|
||||
ports:
|
||||
- "${PGPORT}:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: "${PGUSER}"
|
||||
POSTGRES_PASSWORD: "${PGPASSWORD}"
|
||||
POSTGRES_DB: "${PGDATABASE}"
|
||||
|
||||
volumes:
|
||||
postgres_data: {}
|
||||
redis_data: {}
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
# shellcheck source=test-env.sh
|
||||
source "$DIR/test-env.sh"
|
||||
|
||||
compose_down() {
|
||||
docker compose -f "$DIR/docker-compose.yml" down -v
|
||||
}
|
||||
|
||||
docker compose -f "$DIR/docker-compose.yml" up -d
|
||||
|
||||
trap compose_down EXIT SIGTERM
|
||||
|
||||
docker compose -f "$DIR/docker-compose.yml" logs --follow --timestamps &
|
||||
|
||||
sleep 5
|
||||
|
||||
"$@"
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export NODE_ENV=test
|
||||
|
||||
export REDIS_PORT="16379"
|
||||
export REDIS_URL="redis://localhost:${REDIS_PORT}/0"
|
||||
|
||||
export PGUSER="postgresuser"
|
||||
export PGPASSWORD="handkerchief-break-popular-population"
|
||||
export PGDATABASE="test"
|
||||
export PGPORT="25432"
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getContainerRuntimeClient, ImageName } from 'testcontainers';
|
||||
|
||||
export default async function (globalConfig, projectConfig) {
|
||||
const client = await getContainerRuntimeClient();
|
||||
for (const i of ['redis', 'postgres:14', 'testcontainers/ryuk:0.11.0']) {
|
||||
await client.image.pull(ImageName.fromString(i));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user