0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-07 23:01:25 -05:00

test(core): add tests for database insert-into.ts (#99)

This commit is contained in:
Gao Sun 2021-08-31 00:48:33 +08:00 committed by GitHub
parent bb040cbeee
commit 635fa92853
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 135 additions and 20 deletions

View file

@ -2,6 +2,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFilesAfterEnv: ['jest-matcher-specific-error'],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.test.json',

View file

@ -55,6 +55,7 @@
"@types/oidc-provider": "^7.4.1",
"eslint": "^7.30.0",
"jest": "^27.0.6",
"jest-matcher-specific-error": "^1.0.0",
"lint-staged": "^11.1.1",
"openapi-types": "^9.1.0",
"prettier": "^2.3.2",

View file

@ -0,0 +1,68 @@
import { UserDBEntry, Users } from '@logto/schemas';
import RequestError from '@/errors/RequestError';
import { createTestPool } from '@/utils/test-utils';
import { buildUpdateWhere } from './update-where';
describe('buildUpdateWhere()', () => {
it('resolves a promise with `undefined` when `returning` is false', async () => {
const pool = createTestPool(
'update "users"\nset "username"=$1\nwhere "id"=$2 and "username"=$3'
);
const updateWhere = buildUpdateWhere(pool, Users);
await expect(
updateWhere({ set: { username: '123' }, where: { id: 'foo', username: '456' } })
).resolves.toBe(undefined);
});
it('resolves a promise with single entity when `returning` is true', async () => {
const user: UserDBEntry = { id: 'foo', username: '123', primaryEmail: 'foo@bar.com' };
const pool = createTestPool(
'update "users"\nset "username"=$1, "primary_email"=$2\nwhere "id"=$3\nreturning *',
(_, [username, primaryEmail, id]) => ({
id: String(id),
username: String(username),
primaryEmail: String(primaryEmail),
})
);
const updateWhere = buildUpdateWhere(pool, Users, true);
await expect(
updateWhere({ set: { username: '123', primaryEmail: 'foo@bar.com' }, where: { id: 'foo' } })
).resolves.toStrictEqual(user);
});
it('throws `entity.not_exists_with_id` error with `undefined` when `returning` is true', async () => {
const pool = createTestPool('update "users"\nset "username"=$1\nwhere "id"=$2\nreturning *');
const updateWhere = buildUpdateWhere(pool, Users, true);
await expect(
updateWhere({ set: { username: '123' }, where: { id: 'foo' } })
).rejects.toMatchError(
new RequestError({
code: 'entity.not_exists_with_id',
name: Users.tableSingular,
id: 'foo',
status: 404,
})
);
});
it('throws `entity.not_exists` error with `undefined` when `returning` is true and no id in where clause', async () => {
const pool = createTestPool(
'update "users"\nset "username"=$1\nwhere "username"=$2\nreturning *'
);
const updateWhere = buildUpdateWhere(pool, Users, true);
await expect(
updateWhere({ set: { username: '123' }, where: { username: 'foo' } })
).rejects.toMatchError(
new RequestError({
code: 'entity.not_exists',
name: Users.tableSingular,
id: undefined,
status: 404,
})
);
});
});

View file

@ -1,10 +1,9 @@
import assert from 'assert';
import { notFalsy, Truthy } from '@logto/essentials';
import { SchemaLike, GeneratedSchema } from '@logto/schemas';
import { DatabasePoolType, sql } from 'slonik';
import RequestError from '@/errors/RequestError';
import assert from '@/utils/assert';
import { isKeyOf } from '@/utils/schema';
import { conditionalSql, convertToIdentifiers, convertToPrimitiveOrSql } from './utils';
@ -48,18 +47,19 @@ export const buildUpdateWhere: BuildUpdateWhere = <Schema extends SchemaLike>(
} = await pool.query<Schema>(sql`
update ${table}
set ${sql.join(connectKeyValueWithEqualSign(set), sql`, `)}
where ${sql.join(connectKeyValueWithEqualSign(where), sql`, `)}
where ${sql.join(connectKeyValueWithEqualSign(where), sql` and `)}
${conditionalSql(returning, () => sql`returning *`)}
`);
assert(
!returning || entry,
new RequestError({
code: where.id ? 'entity.not_exists_with_id' : 'entity.not_exists',
name: schema.tableSingular,
id: where.id,
status: 404,
})
() =>
new RequestError({
code: where.id ? 'entity.not_exists_with_id' : 'entity.not_exists',
name: schema.tableSingular,
id: where.id,
status: 404,
})
);
return entry;
};

View file

@ -0,0 +1,16 @@
// https://github.com/facebook/jest/issues/7547
export type AssertFunction = <E extends Error>(
value: unknown,
buildError: () => E
) => asserts value;
const assert: AssertFunction = (value, buildError): asserts value => {
if (!value) {
// https://github.com/typescript-eslint/typescript-eslint/issues/3814
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw buildError();
}
};
export default assert;

View file

@ -1,5 +1,3 @@
import { AssertionError } from 'assert';
import { assertEnv, getEnv } from './env';
describe('getEnv()', () => {
@ -26,8 +24,6 @@ describe('assertEnv()', () => {
});
it("throws an error if env doesn't exist", () => {
expect(() => assertEnv('BAR')).toThrow(
new AssertionError({ message: 'env variable BAR not found' })
);
expect(() => assertEnv('BAR')).toThrow('env variable BAR not found');
});
});

View file

@ -0,0 +1,23 @@
import { createMockPool, createMockQueryResult, QueryResultRowType } from 'slonik';
import { PrimitiveValueExpressionType } from 'slonik/dist/src/types.d';
export const createTestPool = <T extends QueryResultRowType>(
expectSql?: string,
returning?: T | ((sql: string, values: readonly PrimitiveValueExpressionType[]) => T)
) =>
createMockPool({
query: async (sql, values) => {
if (expectSql) {
expect(
sql
.split('\n')
.map((row) => row.trim())
.filter((row) => row)
).toEqual(expectSql.split('\n'));
}
return createMockQueryResult(
returning ? [typeof returning === 'function' ? returning(sql, values) : returning] : []
);
},
});

View file

@ -1,5 +1,12 @@
{
"extends": "./tsconfig.base",
"compilerOptions": {
"types": [
"node",
"jest",
"jest-matcher-specific-error"
]
},
"include": [
"src"
]

View file

@ -1,9 +1,6 @@
{
"extends": "./tsconfig.base",
"extends": "./tsconfig",
"compilerOptions": {
"isolatedModules": false
},
"include": [
"src/**/*.test.ts"
]
"isolatedModules": false,
}
}

6
pnpm-lock.yaml generated
View file

@ -42,6 +42,7 @@ importers:
got: ^11.8.2
i18next: ^20.3.5
jest: ^27.0.6
jest-matcher-specific-error: ^1.0.0
jose: ^3.14.3
koa: ^2.13.1
koa-body: ^4.2.0
@ -102,6 +103,7 @@ importers:
'@types/oidc-provider': 7.4.2
eslint: 7.31.0
jest: 27.0.6
jest-matcher-specific-error: 1.0.0
lint-staged: 11.1.1
openapi-types: 9.1.0
prettier: 2.3.2
@ -9584,6 +9586,10 @@ packages:
pretty-format: 27.0.6
dev: true
/jest-matcher-specific-error/1.0.0:
resolution: {integrity: sha512-thJdy9ibhDo8k+0arFalNCQBJ0u7eqTfpTzS2MzL3iCLmbRCkI+yhhKSiAxEi55e5ZUyf01ySa0fMqzF+sblAw==}
dev: true
/jest-matcher-utils/26.6.2:
resolution: {integrity: sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==}
engines: {node: '>= 10.14.2'}