0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00

test(core): add ut for application query (#282)

add ut for appliction query
This commit is contained in:
simeng-li 2022-02-25 13:52:29 +08:00 committed by GitHub
parent 409602beff
commit 2109b7be31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 196 additions and 11 deletions

View file

@ -67,6 +67,7 @@
"nock": "^13.2.2",
"openapi-types": "^9.1.0",
"prettier": "^2.3.2",
"snake-case": "^3.0.4",
"supertest": "^6.2.2",
"ts-jest": "^27.0.5",
"tsc-watch": "^4.4.0",

View file

@ -0,0 +1,176 @@
import { Applications } from '@logto/schemas';
import {
createMockPool,
createMockQueryResult,
sql,
QueryResultType,
QueryResultRowType,
} from 'slonik';
import { PrimitiveValueExpressionType } from 'slonik/dist/src/types.d';
import { snakeCase } from 'snake-case';
import {
convertToIdentifiers,
convertToPrimitiveOrSql,
excludeAutoSetFields,
} from '@/database/utils';
import { DeletionError } from '@/errors/SlonikError';
import { mockApplication } from '@/utils/mock';
import { expectSqlAssert } from '@/utils/test-utils';
import {
findTotalNumberOfApplications,
findAllApplications,
findApplicationById,
insertApplication,
updateApplicationById,
deleteApplicationById,
} from './application';
type MockQuery = (
sql: string,
values: PrimitiveValueExpressionType
) => Promise<QueryResultType<QueryResultRowType>>;
const mockQuery: jest.MockedFunction<MockQuery> = jest.fn();
jest.mock('@/database/pool', () =>
createMockPool({
query: async (sql, values) => {
return mockQuery(sql, values);
},
})
);
describe('appliaction query', () => {
const { table, fields } = convertToIdentifiers(Applications);
it('findTotalNumberOfApplications', async () => {
const expectSql = sql`
select count(*)
from ${table}
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual(expectSql.values);
return createMockQueryResult([{ count: 10 }]);
});
await expect(findTotalNumberOfApplications()).resolves.toEqual({ count: 10 });
});
it('findAllApplications', async () => {
const limit = 10;
const offset = 1;
const rowData = { id: 'foo' };
const expectSql = sql`
select ${sql.join(Object.values(fields), sql`, `)}
from ${table}
limit $1
offset $2
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([limit, offset]);
return createMockQueryResult([rowData]);
});
await expect(findAllApplications(limit, offset)).resolves.toEqual([rowData]);
});
it('findApplicationById', async () => {
const id = 'foo';
const rowData = { id };
const expectSql = sql`
select ${sql.join(Object.values(fields), sql`, `)}
from ${table}
where ${fields.id}=$1
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([id]);
return createMockQueryResult([rowData]);
});
await findApplicationById(id);
});
it('insertApplication', async () => {
const keys = excludeAutoSetFields(Applications.fieldKeys);
const expectSql = `
insert into "applications" (${keys.map((k) => `"${snakeCase(k)}"`).join(', ')})
values (${keys.map((_, index) => `$${index + 1}`).join(', ')})
returning *
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
const rowData = { id: 'foo' };
expectSqlAssert(sql, expectSql);
expect(values).toEqual(keys.map((k) => convertToPrimitiveOrSql(k, mockApplication[k])));
return createMockQueryResult([rowData]);
});
await insertApplication(mockApplication);
});
it('updateApplicationById', async () => {
const id = 'foo';
const description = 'des';
const expectSql = sql`
update ${table}
set ${fields.description}=$1
where ${fields.id}=$2
returning *
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([description, id]);
return createMockQueryResult([{ id, description }]);
});
await updateApplicationById(id, { description });
});
it('deleteApplicationById', async () => {
const id = 'foo';
const expectSql = sql`
delete from ${table}
where ${fields.id}=$1
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([id]);
return createMockQueryResult([{ id }]);
});
await deleteApplicationById(id);
});
it('deleteApplicationById throw error if return row count is 0', async () => {
const id = 'foo';
mockQuery.mockImplementationOnce(async () => {
return createMockQueryResult([]);
});
await expect(deleteApplicationById(id)).rejects.toMatchError(
new DeletionError(Applications.table, id)
);
});
});

View file

@ -46,7 +46,7 @@ export const updateApplicationById = async (
export const deleteApplicationById = async (id: string) => {
const { rowCount } = await pool.query(sql`
delete from ${table}
where id=${id}
where ${fields.id}=${id}
`);
if (rowCount < 1) {

View file

@ -8,6 +8,20 @@ import request from 'supertest';
import { AuthedRouter, AnonymousRouter } from '@/routes/types';
export const expectSqlAssert = (sql: string, expectSql: string) => {
expect(
sql
.split('\n')
.map((row) => row.trim())
.filter((row) => row)
).toEqual(
expectSql
.split('\n')
.map((row) => row.trim())
.filter((row) => row)
);
};
export const createTestPool = <T extends QueryResultRowType>(
expectSql?: string,
returning?: T | ((sql: string, values: readonly PrimitiveValueExpressionType[]) => T)
@ -15,12 +29,7 @@ export const createTestPool = <T extends QueryResultRowType>(
createMockPool({
query: async (sql, values) => {
if (expectSql) {
expect(
sql
.split('\n')
.map((row) => row.trim())
.filter((row) => row)
).toEqual(expectSql.split('\n'));
expectSqlAssert(sql, expectSql);
}
return createMockQueryResult(

7
pnpm-lock.yaml generated
View file

@ -110,6 +110,7 @@ importers:
query-string: ^7.0.1
slonik: ^23.8.3
slonik-interceptor-preset: ^1.2.10
snake-case: ^3.0.4
snakecase-keys: ^5.1.0
supertest: ^6.2.2
ts-jest: ^27.0.5
@ -165,6 +166,7 @@ importers:
nock: 13.2.2
openapi-types: 9.3.1
prettier: 2.5.1
snake-case: 3.0.4
supertest: 6.2.2
ts-jest: 27.1.1_0ef321b3552d50570980838f9f6677eb
tsc-watch: 4.5.0_typescript@4.5.5
@ -5066,7 +5068,6 @@ packages:
dependencies:
no-case: 3.0.4
tslib: 2.3.1
dev: false
/dot-prop/5.3.0:
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
@ -6285,6 +6286,7 @@ packages:
/graceful-fs/4.2.9:
resolution: {integrity: sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==}
requiresBuild: true
dev: true
/handlebars/4.7.7:
@ -8704,7 +8706,6 @@ packages:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
dependencies:
tslib: 2.3.1
dev: false
/lowercase-keys/1.0.1:
resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==}
@ -9179,7 +9180,6 @@ packages:
dependencies:
lower-case: 2.0.2
tslib: 2.3.1
dev: false
/nock/13.2.2:
resolution: {integrity: sha512-PcBHuvl9i6zfaJ50A7LS55oU+nFLv8htXIhffJO+FxyfibdZ4jEvd9kTuvkrJireBFIGMZ+oUIRpMK5gU9h//g==}
@ -11588,7 +11588,6 @@ packages:
dependencies:
dot-case: 3.0.4
tslib: 2.3.1
dev: false
/snakecase-keys/5.1.2:
resolution: {integrity: sha512-fvtDQZqPBqYb0dEY97TGuOMbN2NJ05Tj4MaoKwjTKkmjcG6mrd58JYGr23UWZRi6Aqv49Fk4HtjTIStOQenaug==}