0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00
logto/packages/core/src/queries/user.test.ts
Darcy Ye 82d104a0d3
feat(core): add DELETE /users/:userId/identities/:connectorId (#437)
* feat(core): add DELETE /users/:userId/identities/:connectorId

* feat(core): add user query methods UT cases for better testing coverage

* feat(core): rewrite deletion of connector info from user identities using postgresql operator
2022-03-25 15:48:53 +08:00

426 lines
12 KiB
TypeScript

import { Users } from '@logto/schemas';
import { createMockPool, createMockQueryResult, sql } from 'slonik';
import { convertToIdentifiers, convertToPrimitiveOrSql } from '@/database/utils';
import { DeletionError } from '@/errors/SlonikError';
import { mockUser } from '@/utils/mock';
import { expectSqlAssert, QueryType } from '@/utils/test-utils';
import {
findUserByUsername,
findUserByEmail,
findUserByPhone,
findUserById,
findUserByIdentity,
hasUser,
hasUserWithId,
hasUserWithEmail,
hasUserWithIdentity,
hasUserWithPhone,
insertUser,
countUsers,
findUsers,
updateUserById,
deleteUserById,
clearUserCustomDataById,
deleteUserIdentity,
} from './user';
const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
jest.mock('@/database/pool', () =>
createMockPool({
query: async (sql, values) => {
return mockQuery(sql, values);
},
})
);
describe('user query', () => {
const { table, fields } = convertToIdentifiers(Users);
const dbvalue = {
...mockUser,
roleNames: JSON.stringify(mockUser.roleNames),
identities: JSON.stringify(mockUser.identities),
customData: JSON.stringify(mockUser.customData),
};
it('findUserByUsername', async () => {
const expectSql = sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where ${fields.username}=$1
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([mockUser.username]);
return createMockQueryResult([dbvalue]);
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await expect(findUserByUsername(mockUser.username!)).resolves.toEqual(dbvalue);
});
it('findUserByEmail', async () => {
const expectSql = sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where ${fields.primaryEmail}=$1
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([mockUser.primaryEmail]);
return createMockQueryResult([dbvalue]);
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await expect(findUserByEmail(mockUser.primaryEmail!)).resolves.toEqual(dbvalue);
});
it('findUserByPhone', async () => {
const expectSql = sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where ${fields.primaryPhone}=$1
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([mockUser.primaryPhone]);
return createMockQueryResult([dbvalue]);
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await expect(findUserByPhone(mockUser.primaryPhone!)).resolves.toEqual(dbvalue);
});
it('findUserById', async () => {
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([mockUser.id]);
return createMockQueryResult([dbvalue]);
});
await expect(findUserById(mockUser.id)).resolves.toEqual(dbvalue);
});
it('findUserByIdentity', async () => {
const connectorId = 'github_foo';
const expectSql = sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where ${fields.identities}::json#>>'{${sql.identifier([connectorId])},userId}' = $1
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([mockUser.id]);
return createMockQueryResult([dbvalue]);
});
await expect(findUserByIdentity(connectorId, mockUser.id)).resolves.toEqual(dbvalue);
});
it('hasUser', async () => {
const expectSql = sql`
SELECT EXISTS(
select ${fields.id}
from ${table}
where ${fields.username}=$1
)
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([mockUser.username]);
return createMockQueryResult([{ exists: true }]);
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await expect(hasUser(mockUser.username!)).resolves.toEqual(true);
});
it('hasUserWithId', async () => {
const expectSql = sql`
SELECT EXISTS(
select ${fields.id}
from ${table}
where ${fields.id}=$1
)
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([mockUser.id]);
return createMockQueryResult([{ exists: true }]);
});
await expect(hasUserWithId(mockUser.id)).resolves.toEqual(true);
});
it('hasUserWithEmail', async () => {
const expectSql = sql`
SELECT EXISTS(
select ${fields.primaryEmail}
from ${table}
where ${fields.primaryEmail}=$1
)
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([mockUser.primaryEmail]);
return createMockQueryResult([{ exists: true }]);
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await expect(hasUserWithEmail(mockUser.primaryEmail!)).resolves.toEqual(true);
});
it('hasUserWithPhone', async () => {
const expectSql = sql`
SELECT EXISTS(
select ${fields.primaryPhone}
from ${table}
where ${fields.primaryPhone}=$1
)
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([mockUser.primaryPhone]);
return createMockQueryResult([{ exists: true }]);
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await expect(hasUserWithPhone(mockUser.primaryPhone!)).resolves.toEqual(true);
});
it('hasUserWithIdentity', async () => {
const connectorId = 'github_foo';
const expectSql = sql`
SELECT EXISTS(
select ${fields.id}
from ${table}
where ${fields.identities}::json#>>'{${sql.identifier([connectorId])},userId}' = $1
)
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([mockUser.id]);
return createMockQueryResult([{ exists: true }]);
});
await expect(hasUserWithIdentity(connectorId, mockUser.id)).resolves.toEqual(true);
});
it('insertUser', async () => {
const expectSql = sql`
insert into ${table} (${sql.join(Object.values(fields), sql`, `)})
values (${sql.join(
Object.values(fields).map((_, index) => `$${index + 1}`),
sql`, `
)})
returning *
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual(Users.fieldKeys.map((k) => convertToPrimitiveOrSql(k, mockUser[k])));
return createMockQueryResult([dbvalue]);
});
await expect(insertUser(mockUser)).resolves.toEqual(dbvalue);
});
it('countUsers', async () => {
const search = 'foo';
const expectSql = sql`
select count(*)
from ${table}
where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${fields.username} like $3 or ${fields.name} like $4
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([`%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`]);
return createMockQueryResult([dbvalue]);
});
await expect(countUsers(search)).resolves.toEqual(dbvalue);
});
it('findUsers', async () => {
const search = 'foo';
const limit = 100;
const offset = 1;
const expectSql = sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${
fields.username
} like $3 or ${fields.name} like $4
limit $5
offset $6
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([
`%${search}%`,
`%${search}%`,
`%${search}%`,
`%${search}%`,
limit,
offset,
]);
return createMockQueryResult([dbvalue]);
});
await expect(findUsers(limit, offset, search)).resolves.toEqual([dbvalue]);
});
it('updateUserById', async () => {
const username = 'Joe';
const id = 'foo';
const expectSql = sql`
update ${table}
set ${fields.username}=$1
where ${fields.id}=$2
returning *
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([username, id]);
return createMockQueryResult([dbvalue]);
});
await expect(updateUserById(id, { username })).resolves.toEqual(dbvalue);
});
it('deleteUserById', 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([dbvalue]);
});
await deleteUserById(id);
});
it('deleteUserById should throw with zero response', 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([]);
});
await expect(deleteUserById(id)).rejects.toMatchError(new DeletionError(Users.table, id));
});
it('clearUserCustomDataById', async () => {
const id = 'foo';
const expectSql = sql`
update ${table}
set ${fields.customData}='{}'::jsonb
where ${fields.id}=$1
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([id]);
return createMockQueryResult([dbvalue]);
});
await clearUserCustomDataById(id);
});
it('clearUserCustomDataById should throw when user can not be found by id', async () => {
const id = 'foo';
const expectSql = sql`
update ${table}
set ${fields.customData}='{}'::jsonb
where ${fields.id}=$1
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([id]);
return createMockQueryResult([]);
});
await expect(clearUserCustomDataById(id)).rejects.toThrowError();
});
it('deleteUserIdentity', async () => {
const userId = 'foo';
const connectorId = 'connector1';
const { connector1, ...restIdentities } = mockUser.identities;
const finalDbvalue = {
...mockUser,
roleNames: JSON.stringify(mockUser.roleNames),
identities: JSON.stringify(restIdentities),
customData: JSON.stringify(mockUser.customData),
};
const expectSql = sql`
update ${table}
set ${fields.identities}=${fields.identities}::jsonb-$1
where ${fields.id}=$2
returning *
`;
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql.sql);
expect(values).toEqual([connectorId, mockUser.id]);
return createMockQueryResult([finalDbvalue]);
});
await expect(deleteUserIdentity(userId, connectorId)).resolves.toEqual(finalDbvalue);
});
});