0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

refactor: use ky in integration tests (#5584)

* refactor: use ky in integration tests

* refactor: remove node-fetch

* refactor: fix test cases

* refactor: remove waitFor after each test
This commit is contained in:
Gao Sun 2024-03-29 18:10:13 +08:00 committed by GitHub
parent eb4c6c4e59
commit 6d56434b48
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
85 changed files with 556 additions and 582 deletions

View file

@ -84,7 +84,7 @@
"jest-transform-stub": "^2.0.0",
"jest-transformer-svg": "^2.0.0",
"just-kebab-case": "^4.2.0",
"ky": "^1.0.0",
"ky": "^1.2.3",
"libphonenumber-js": "^1.10.51",
"lint-staged": "^15.0.0",
"nanoid": "^5.0.1",

View file

@ -67,7 +67,7 @@
"jest-transform-stub": "^2.0.0",
"jest-transformer-svg": "^2.0.0",
"js-base64": "^3.7.5",
"ky": "^1.0.0",
"ky": "^1.2.3",
"libphonenumber-js": "^1.10.51",
"lint-staged": "^15.0.0",
"parcel": "2.9.3",

View file

@ -5,13 +5,3 @@ import { authedAdminTenantApi } from './lib/api/api.js';
await authedAdminTenantApi.patch('sign-in-exp', {
json: { signInMode: 'SignInAndRegister' },
});
const waitFor = async (ms) =>
new Promise((resolve) => {
setTimeout(resolve, ms);
});
global.afterEach(async () => {
// Try to mitigate the issue of "Socket hang up". See https://github.com/nodejs/node/issues/47130
await waitFor(1);
});

View file

@ -1,6 +1,5 @@
import dotenv from 'dotenv';
import { setDefaultOptions } from 'expect-puppeteer';
import fetch from 'node-fetch';
import { TextDecoder, TextEncoder } from 'text-encoder';
const { jest } = import.meta;
@ -8,9 +7,12 @@ const { jest } = import.meta;
dotenv.config();
/* eslint-disable @silverhand/fp/no-mutation */
global.fetch = fetch;
global.TextDecoder = TextDecoder;
global.TextEncoder = TextEncoder;
global.fail = (message) => {
throw new Error(message);
};
/* eslint-enable @silverhand/fp/no-mutation */
// GitHub Actions default runners need more time for UI tests

View file

@ -38,12 +38,11 @@
"dotenv": "^16.0.0",
"eslint": "^8.44.0",
"expect-puppeteer": "^10.0.0",
"got": "^14.0.0",
"jest": "^29.7.0",
"jest-matcher-specific-error": "^1.0.0",
"jest-puppeteer": "^10.0.1",
"jose": "^5.0.0",
"node-fetch": "^3.3.0",
"ky": "^1.2.3",
"openapi-schema-validator": "^12.1.3",
"openapi-types": "^12.1.3",
"prettier": "^3.0.0",

View file

@ -1,9 +1,9 @@
import { appendPath } from '@silverhand/essentials';
import { got } from 'got';
import ky from 'ky';
import { logtoConsoleUrl, logtoUrl, logtoCloudUrl } from '#src/constants.js';
const api = got.extend({
const api = ky.extend({
prefixUrl: appendPath(new URL(logtoUrl), 'api'),
});
@ -16,7 +16,7 @@ export const authedAdminApi = api.extend({
},
});
export const adminTenantApi = got.extend({
export const adminTenantApi = ky.extend({
prefixUrl: appendPath(new URL(logtoConsoleUrl), 'api'),
});
@ -26,10 +26,10 @@ export const authedAdminTenantApi = adminTenantApi.extend({
},
});
export const cloudApi = got.extend({
export const cloudApi = ky.extend({
prefixUrl: appendPath(new URL(logtoCloudUrl), 'api'),
});
export const oidcApi = got.extend({
export const oidcApi = ky.extend({
prefixUrl: appendPath(new URL(logtoUrl), 'oidc'),
});

View file

@ -99,10 +99,13 @@ export const generateM2mLog = async (applicationId: string) => {
// This is a token request with insufficient parameters and should fail. We make the request to generate a log for the current machine to machine app.
return oidcApi.post('token', {
form: {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: id,
client_secret: secret,
grant_type: 'client_credentials',
},
}),
});
};

View file

@ -31,14 +31,13 @@ export const postConnector = async (
payload: Pick<CreateConnector, 'connectorId' | 'config' | 'metadata' | 'syncProfile'>
) =>
authedAdminApi
.post({
url: `connectors`,
.post('connectors', {
json: payload,
})
.json<Connector>();
export const deleteConnectorById = async (id: string) =>
authedAdminApi.delete({ url: `connectors/${id}` }).json();
authedAdminApi.delete(`connectors/${id}`).json();
export const updateConnectorConfig = async (
id: string,
@ -46,8 +45,7 @@ export const updateConnectorConfig = async (
metadata?: Record<string, unknown>
) =>
authedAdminApi
.patch({
url: `connectors/${id}`,
.patch(`connectors/${id}`, {
json: { config, metadata },
})
.json<ConnectorResponse>();
@ -70,8 +68,7 @@ const sendTestMessage = async (
receiver: string,
config: Record<string, unknown>
) =>
authedAdminApi.post({
url: `connectors/${connectorFactoryId}/test`,
authedAdminApi.post(`connectors/${connectorFactoryId}/test`, {
json: { [receiverType]: receiver, config },
});
@ -81,8 +78,7 @@ export const getConnectorAuthorizationUri = async (
redirectUri: string
) =>
authedAdminApi
.post({
url: `connectors/${connectorId}/authorization-uri`,
.post(`connectors/${connectorId}/authorization-uri`, {
json: { state, redirectUri },
})
.json<{ redirectTo: string }>();

View file

@ -9,9 +9,7 @@ export const getCustomPhrase = async (languageTag: string) =>
authedAdminApi.get(`custom-phrases/${languageTag}`).json<CustomPhrase>();
export const createOrUpdateCustomPhrase = async (languageTag: string, translation: Translation) =>
authedAdminApi
.put({ url: `custom-phrases/${languageTag}`, json: translation })
.json<CustomPhrase>();
authedAdminApi.put(`custom-phrases/${languageTag}`, { json: translation }).json<CustomPhrase>();
export const deleteCustomPhrase = async (languageTag: string) =>
authedAdminApi.delete(`custom-phrases/${languageTag}`).json();

View file

@ -1,5 +1,23 @@
import { authedAdminApi } from './api.js';
/**
* Transform the data to a new object or array without modifying the original data.
* This is useful since `.json()` returns an object that contains something which makes
* it impossible to use `.toStrictEqual()` directly.
*/
const transform = <T>(data: T): T => {
if (Array.isArray(data)) {
// eslint-disable-next-line no-restricted-syntax
return [...data] as T;
}
if (typeof data === 'object') {
return { ...data };
}
return data;
};
export class ApiFactory<
Schema extends Record<string, unknown>,
PostData extends Record<string, unknown>,
@ -8,19 +26,23 @@ export class ApiFactory<
constructor(public readonly path: string) {}
async create(data: PostData): Promise<Schema> {
return authedAdminApi.post(this.path, { json: data }).json<Schema>();
return transform(await authedAdminApi.post(this.path, { json: data }).json<Schema>());
}
async getList(params?: URLSearchParams): Promise<Schema[]> {
return authedAdminApi.get(this.path + '?' + (params?.toString() ?? '')).json<Schema[]>();
return transform(
await authedAdminApi.get(this.path + '?' + (params?.toString() ?? '')).json<Schema[]>()
);
}
async get(id: string): Promise<Schema> {
return authedAdminApi.get(this.path + '/' + id).json<Schema>();
return transform(await authedAdminApi.get(this.path + '/' + id).json<Schema>());
}
async update(id: string, data: PatchData): Promise<Schema> {
return authedAdminApi.patch(this.path + '/' + id, { json: data }).json<Schema>();
return transform(
await authedAdminApi.patch(this.path + '/' + id, { json: data }).json<Schema>()
);
}
async delete(id: string): Promise<void> {

View file

@ -15,7 +15,6 @@ export const getSsoAuthorizationUrl = async (
.post(`interaction/${ssoPath}/${connectorId}/authorization-url`, {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json<{ redirectTo: string }>();
};

View file

@ -7,7 +7,7 @@ import type {
VerifyMfaPayload,
ConsentInfoResponse,
} from '@logto/schemas';
import type { Got } from 'got';
import { type KyInstance } from 'ky';
import api from './api.js';
@ -26,7 +26,6 @@ export const putInteraction = async (cookie: string, payload: InteractionPayload
.put('interaction', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();
@ -34,21 +33,17 @@ export const deleteInteraction = async (cookie: string) =>
api
.delete('interaction', {
headers: { cookie },
followRedirect: false,
})
.json();
export const putInteractionEvent = async (cookie: string, payload: { event: InteractionEvent }) =>
api
.put('interaction/event', { headers: { cookie }, json: payload, followRedirect: false })
.json();
api.put('interaction/event', { headers: { cookie }, json: payload, redirect: 'manual' }).json();
export const patchInteractionIdentifiers = async (cookie: string, payload: IdentifierPayload) =>
api
.patch('interaction/identifiers', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();
@ -57,7 +52,6 @@ export const patchInteractionProfile = async (cookie: string, payload: Profile)
.patch('interaction/profile', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();
@ -66,7 +60,6 @@ export const putInteractionProfile = async (cookie: string, payload: Profile) =>
.put('interaction/profile', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();
@ -75,7 +68,6 @@ export const postInteractionBindMfa = async (cookie: string, payload: BindMfaPay
.post('interaction/bind-mfa', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();
@ -84,7 +76,6 @@ export const putInteractionMfa = async (cookie: string, payload: VerifyMfaPayloa
.put('interaction/mfa', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();
@ -92,13 +83,12 @@ export const deleteInteractionProfile = async (cookie: string) =>
api
.delete('interaction/profile', {
headers: { cookie },
followRedirect: false,
})
.json();
export const submitInteraction = async (api: Got, cookie: string) =>
export const submitInteraction = async (api: KyInstance, cookie: string) =>
api
.post('interaction/submit', { headers: { cookie }, followRedirect: false })
.post('interaction/submit', { headers: { cookie }, redirect: 'manual' })
.json<RedirectResponse>();
export const sendVerificationCode = async (
@ -108,7 +98,6 @@ export const sendVerificationCode = async (
api.post('interaction/verification/verification-code', {
headers: { cookie },
json: payload,
followRedirect: false,
});
export type SocialAuthorizationUriPayload = {
@ -124,7 +113,6 @@ export const createSocialAuthorizationUri = async (
api.post('interaction/verification/social-authorization-uri', {
headers: { cookie },
json: payload,
followRedirect: false,
});
export const initTotp = async (cookie: string) =>
@ -132,7 +120,8 @@ export const initTotp = async (cookie: string) =>
.post('interaction/verification/totp', {
headers: { cookie },
json: {},
followRedirect: false,
redirect: 'manual',
throwHttpErrors: false,
})
.json<{ secret: string }>();
@ -142,16 +131,16 @@ export const skipMfaBinding = async (cookie: string) =>
json: {
mfaSkipped: true,
},
followRedirect: false,
});
export const consent = async (api: Got, cookie: string) =>
export const consent = async (api: KyInstance, cookie: string) =>
api
.post('interaction/consent', {
headers: {
cookie,
},
followRedirect: false,
redirect: 'manual',
throwHttpErrors: false,
})
.json<RedirectResponse>();
@ -159,7 +148,6 @@ export const getConsentInfo = async (cookie: string) =>
api
.get('interaction/consent', {
headers: { cookie },
followRedirect: false,
})
.json<ConsentInfoResponse>();
@ -170,5 +158,4 @@ export const createSingleSignOnAuthorizationUri = async (
api.post('interaction/verification/sso-authorization-uri', {
headers: { cookie },
json: payload,
followRedirect: false,
});

View file

@ -37,7 +37,7 @@ export class OrganizationApi extends ApiFactory<
query?: Query
): Promise<[rows: UserWithOrganizationRoles[], totalCount: number]> {
const got = await authedAdminApi.get(`${this.path}/${id}/users`, { searchParams: query });
return [JSON.parse(got.body), Number(got.headers['total-number'] ?? 0)];
return [await got.json(), Number(got.headers.get('total-number') ?? 0)];
}
async deleteUser(id: string, userId: string): Promise<void> {

View file

@ -1,5 +1,5 @@
import type { Resource, CreateResource } from '@logto/schemas';
import type { OptionsOfTextResponseBody } from 'got';
import { type Options } from 'ky';
import { generateResourceIndicator, generateResourceName } from '#src/utils.js';
@ -17,7 +17,7 @@ export const createResource = async (name?: string, indicator?: string) =>
export const getResources = async () => authedAdminApi.get('resources').json<Resource[]>();
export const getResource = async (resourceId: string, options?: OptionsOfTextResponseBody) =>
export const getResource = async (resourceId: string, options?: Options) =>
authedAdminApi.get(`resources/${resourceId}`, options).json<Resource>();
export const updateResource = async (

View file

@ -1,11 +1,11 @@
import type { Scope, CreateScope } from '@logto/schemas';
import type { OptionsOfTextResponseBody } from 'got';
import { type Options } from 'ky';
import { generateScopeName } from '#src/utils.js';
import { authedAdminApi } from './api.js';
export const getScopes = async (resourceId: string, options?: OptionsOfTextResponseBody) =>
export const getScopes = async (resourceId: string, options?: Options) =>
authedAdminApi.get(`resources/${resourceId}/scopes`, options).json<Scope[]>();
export const createScope = async (resourceId: string, name?: string) => {

View file

@ -3,8 +3,7 @@ import LogtoClient from '@logto/node';
import { demoAppApplicationId } from '@logto/schemas';
import type { Nullable, Optional } from '@silverhand/essentials';
import { assert } from '@silverhand/essentials';
import type { Got } from 'got';
import { got } from 'got';
import ky, { type KyInstance } from 'ky';
import { submitInteraction } from '#src/api/index.js';
import { demoAppRedirectUri, logtoUrl } from '#src/constants.js';
@ -23,12 +22,12 @@ export default class MockClient {
protected readonly logto: LogtoClient;
private navigateUrl?: string;
private readonly api: Got;
private readonly api: KyInstance;
constructor(config?: Partial<LogtoConfig>) {
this.storage = new MemoryStorage();
this.config = { ...defaultConfig, ...config };
this.api = got.extend({ prefixUrl: this.config.endpoint + '/api' });
this.api = ky.extend({ prefixUrl: this.config.endpoint + '/api' });
this.logto = new LogtoClient(this.config, {
navigate: (url: string) => {
@ -72,19 +71,22 @@ export default class MockClient {
);
// Mock SDK sign-in navigation
const response = await got(this.navigateUrl, {
followRedirect: false,
const response = await ky(this.navigateUrl, {
redirect: 'manual',
throwHttpErrors: false,
});
// Note: should redirect to sign-in page
assert(
response.statusCode === 303 &&
response.headers.location?.startsWith(options.directSignIn ? '/direct/' : '/sign-in'),
response.status === 303 &&
response.headers
.get('location')
?.startsWith(options.directSignIn ? '/direct/' : '/sign-in'),
new Error('Visit sign in uri failed')
);
// Get session cookie
this.rawCookies = response.headers['set-cookie'] ?? [];
this.rawCookies = response.headers.getSetCookie();
assert(this.interactionCookie, new Error('Get cookie from authorization endpoint failed'));
}
@ -100,20 +102,21 @@ export default class MockClient {
new Error('SignIn or Register failed')
);
const authResponse = await got.get(redirectTo, {
const authResponse = await ky.get(redirectTo, {
headers: {
cookie: this.interactionCookie,
},
followRedirect: false,
redirect: 'manual',
throwHttpErrors: false,
});
// Note: Should redirect to logto consent page
assert(
authResponse.statusCode === 303 && authResponse.headers.location === '/consent',
authResponse.status === 303 && authResponse.headers.get('location') === '/consent',
new Error('Invoke auth before consent failed')
);
this.rawCookies = authResponse.headers['set-cookie'] ?? [];
this.rawCookies = authResponse.headers.getSetCookie();
// Manually handle the consent flow
if (!consent) {
@ -138,7 +141,7 @@ export default class MockClient {
}
await this.logto.signOut(postSignOutRedirectUri);
await got(this.navigateUrl);
await ky(this.navigateUrl);
}
public async isAuthenticated() {
@ -176,30 +179,32 @@ export default class MockClient {
assert(this.interactionCookie, new Error('Session not found'));
assert(this.interactionCookie.includes('_session.sig'), new Error('Session not found'));
const consentResponse = await got.get(`${this.config.endpoint}/consent`, {
const consentResponse = await ky.get(`${this.config.endpoint}/consent`, {
headers: {
cookie: this.interactionCookie,
},
followRedirect: false,
redirect: 'manual',
throwHttpErrors: false,
});
// Consent page should auto consent and redirect to auth endpoint
const redirectTo = consentResponse.headers.location;
const redirectTo = consentResponse.headers.get('location');
if (!redirectTo?.startsWith(`${this.config.endpoint}/oidc/auth`)) {
throw new Error('Consent failed');
}
const authCodeResponse = await got.get(redirectTo, {
const authCodeResponse = await ky.get(redirectTo, {
headers: {
cookie: this.interactionCookie,
},
followRedirect: false,
redirect: 'manual',
throwHttpErrors: false,
});
// Note: Should redirect to the signInCallbackUri
assert(authCodeResponse.statusCode === 303, new Error('Complete auth failed'));
const signInCallbackUri = authCodeResponse.headers.location;
assert(authCodeResponse.status === 303, new Error('Complete auth failed'));
const signInCallbackUri = authCodeResponse.headers.get('location');
assert(signInCallbackUri, new Error('Get sign in callback uri failed'));
return signInCallbackUri;

View file

@ -62,7 +62,8 @@ export const putInteraction = async (cookie: string, payload: InteractionPayload
.put('interaction', {
headers: { cookie },
json: payload,
followRedirect: false,
redirect: 'manual',
throwHttpErrors: false,
})
.json();

View file

@ -7,7 +7,7 @@ import {
type JsonObject,
type UsersPasswordEncryptionMethod,
} from '@logto/schemas';
import { RequestError } from 'got';
import { HTTPError } from 'ky';
import { createUser } from '#src/api/index.js';
import { generateUsername } from '#src/utils.js';
@ -77,7 +77,7 @@ export const removeConnectorMessage = async (
type ExpectedErrorInfo = {
code: string;
statusCode: number;
status: number;
messageIncludes?: string;
};
@ -94,16 +94,16 @@ export const expectRejects = async <T = void>(
fail();
};
export const expectRequestError = <T = void>(error: unknown, expected: ExpectedErrorInfo) => {
const { code, statusCode, messageIncludes } = expected;
const expectRequestError = async <T = void>(error: unknown, expected: ExpectedErrorInfo) => {
const { code, status, messageIncludes } = expected;
if (!(error instanceof RequestError)) {
if (!(error instanceof HTTPError)) {
fail('Error should be an instance of RequestError');
}
// JSON.parse returns `any`. Directly use `as` since we've already know the response body structure.
// eslint-disable-next-line no-restricted-syntax
const body = JSON.parse(String(error.response?.body)) as {
const body = (await error.response.json()) as {
code: string;
message: string;
data: T;
@ -111,7 +111,7 @@ export const expectRequestError = <T = void>(error: unknown, expected: ExpectedE
expect(body.code).toEqual(code);
expect(error.response?.statusCode).toEqual(statusCode);
expect(error.response.status).toEqual(status);
if (messageIncludes) {
expect(body.message.includes(messageIncludes)).toBeTruthy();

View file

@ -80,7 +80,7 @@ export const createNewSocialUserWithUsernameAndPassword = async (connectorId: st
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(patchInteractionIdentifiers, { username, password });
await client.successSend(putInteractionProfile, { connectorId });

View file

@ -1,4 +0,0 @@
declare module 'node-fetch' {
const nodeFetch: typeof fetch;
export = nodeFetch;
}

View file

@ -1,5 +1,5 @@
import { RoleType } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { assignRolesToUser, getUserRoles, deleteRoleFromUser } from '#src/api/index.js';
import { createRole } from '#src/api/role.js';
@ -21,7 +21,7 @@ describe('admin console user management (roles)', () => {
const m2mRole = await createRole({ type: RoleType.MachineToMachine });
await expectRejects(assignRolesToUser(user.id, [m2mRole.id]), {
code: 'user.invalid_role_type',
statusCode: 422,
status: 422,
});
await assignRolesToUser(user.id, [role1.id, role2.id]);
@ -65,6 +65,6 @@ describe('admin console user management (roles)', () => {
const role = await createRole({});
const response = await deleteRoleFromUser(user.id, role.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
});

View file

@ -1,5 +1,3 @@
import type { IncomingHttpHeaders } from 'node:http';
import type { Role, User } from '@logto/schemas';
import { assignRolesToUser, authedAdminApi, createUser, deleteUser } from '#src/api/index.js';
@ -10,12 +8,12 @@ import { UserApiTest } from '#src/helpers/user.js';
const getUsers = async <T>(
init: string[][] | Record<string, string> | URLSearchParams
): Promise<{ headers: IncomingHttpHeaders; json: T }> => {
const { headers, body } = await authedAdminApi.get('users', {
): Promise<{ headers: Headers; json: T }> => {
const response = await authedAdminApi.get('users', {
searchParams: new URLSearchParams(init),
});
return { headers, json: JSON.parse(body) as T };
return { headers: response.headers, json: (await response.json()) as T };
};
describe('admin console user search params', () => {
@ -64,21 +62,21 @@ describe('admin console user search params', () => {
it('should return all users if nothing specified', async () => {
const { headers } = await getUsers<User[]>([]);
expect(Number(headers['total-number'])).toBeGreaterThanOrEqual(10);
expect(Number(headers.get('total-number'))).toBeGreaterThanOrEqual(10);
});
describe('falling back to `like` mode and matches all available fields if only `search` is specified', () => {
it('should search username', async () => {
const { headers, json } = await getUsers<User[]>([['search', '%search_tom%']]);
expect(headers['total-number']).toEqual('5');
expect(headers.get('total-number')).toEqual('5');
expect(json.length === 5 && json.every((user) => user.name === 'Tom Scott')).toBeTruthy();
});
it('should search primaryPhone', async () => {
const { headers, json } = await getUsers<User[]>([['search', '%0000%']]);
expect(headers['total-number']).toEqual('10');
expect(headers.get('total-number')).toEqual('10');
expect(
json.length === 10 && json.every((user) => user.username?.startsWith('search_'))
).toBeTruthy();
@ -92,7 +90,7 @@ describe('admin console user search params', () => {
['isCaseSensitive', 'true'],
]);
expect(headers['total-number']).toEqual('0');
expect(headers.get('total-number')).toEqual('0');
expect(json.length === 0).toBeTruthy();
});
@ -102,7 +100,7 @@ describe('admin console user search params', () => {
['mode.name', 'exact'],
]);
expect(headers['total-number']).toEqual('2');
expect(headers.get('total-number')).toEqual('2');
expect(json.length === 2 && json.every((user) => user.name === 'Jerry Swift')).toBeTruthy();
});
@ -117,7 +115,7 @@ describe('admin console user search params', () => {
['isCaseSensitive', 'true'],
]);
expect(headers['total-number']).toEqual('2');
expect(headers.get('total-number')).toEqual('2');
expect(json.length === 2 && json.every((user) => user.name === 'Jerry Swift Jr')).toBeTruthy();
});
@ -130,7 +128,7 @@ describe('admin console user search params', () => {
['isCaseSensitive', 'true'],
]);
expect(headers['total-number']).toEqual('5');
expect(headers.get('total-number')).toEqual('5');
expect(
json.length === 5 && json.every((user) => user.username?.startsWith('search_'))
).toBeTruthy();
@ -144,7 +142,7 @@ describe('admin console user search params', () => {
['mode.primaryEmail', 'exact'],
]);
expect(headers['total-number']).toEqual('3');
expect(headers.get('total-number')).toEqual('3');
expect(
json.length === 3 && json.every((user) => user.name?.startsWith('Jerry Swift Jr'))
).toBeTruthy();
@ -161,7 +159,7 @@ describe('admin console user search params', () => {
['isCaseSensitive', 'true'],
]);
expect(headers['total-number']).toEqual('3');
expect(headers.get('total-number')).toEqual('3');
expect(
json.length === 3 && json.every((user) => user.username?.startsWith('search_'))
).toBeTruthy();
@ -174,7 +172,7 @@ describe('admin console user search params', () => {
['search.primaryEmail', 'jerry_swift_jr_2@geek.best'],
['search.primaryEmail', 'jerry_swift_jr_jr@gmail.com'],
]),
{ code: 'request.invalid_input', statusCode: 400, messageIncludes: '`exact`' }
{ code: 'request.invalid_input', status: 400, messageIncludes: '`exact`' }
);
});
@ -186,7 +184,7 @@ describe('admin console user search params', () => {
]),
{
code: 'request.invalid_input',
statusCode: 400,
status: 400,
messageIncludes: 'cannot be empty',
}
);
@ -200,7 +198,7 @@ describe('admin console user search params', () => {
]),
{
code: 'request.invalid_input',
statusCode: 400,
status: 400,
messageIncludes: 'case-insensitive',
}
);
@ -215,13 +213,13 @@ describe('admin console user search params', () => {
]),
{
code: 'request.invalid_input',
statusCode: 400,
status: 400,
messageIncludes: 'is not valid',
}
),
expectRejects(getUsers<User[]>([['search.email', '%gmail%']]), {
code: 'request.invalid_input',
statusCode: 400,
status: 400,
messageIncludes: 'is not valid',
}),
expectRejects(
@ -231,7 +229,7 @@ describe('admin console user search params', () => {
]),
{
code: 'request.invalid_input',
statusCode: 400,
status: 400,
messageIncludes: 'is not valid',
}
),
@ -283,7 +281,7 @@ describe('admin console user search params - excludeRoleId', () => {
['excludeRoleId', roles[0]!.id],
]);
expect(headers['total-number']).toEqual('2');
expect(headers.get('total-number')).toEqual('2');
expect(json).toHaveLength(2);
expect(json).toContainEqual(expect.objectContaining({ id: users[1]!.id }));
expect(json).toContainEqual(expect.objectContaining({ id: users[2]!.id }));
@ -295,7 +293,7 @@ describe('admin console user search params - excludeRoleId', () => {
['excludeRoleId', roles[1]!.id],
]);
expect(headers['total-number']).toEqual('1');
expect(headers.get('total-number')).toEqual('1');
expect(json).toHaveLength(1);
expect(json).toContainEqual(expect.objectContaining({ id: users[2]!.id }));
});
@ -341,7 +339,7 @@ describe('admin console user search params - excludeOrganizationId', () => {
['excludeOrganizationId', organizationApi.organizations[0]!.id],
]);
expect(headers['total-number']).toEqual('1');
expect(headers.get('total-number')).toEqual('1');
expect(json).toHaveLength(1);
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[2]!.id }));
});
@ -352,7 +350,7 @@ describe('admin console user search params - excludeOrganizationId', () => {
['excludeOrganizationId', organizationApi.organizations[1]!.id],
]);
expect(headers['total-number']).toEqual('1');
expect(headers.get('total-number')).toEqual('1');
expect(json).toHaveLength(1);
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[0]!.id }));
});
@ -363,7 +361,7 @@ describe('admin console user search params - excludeOrganizationId', () => {
['excludeOrganizationId', organizationApi.organizations[2]!.id],
]);
expect(headers['total-number']).toEqual('2');
expect(headers.get('total-number')).toEqual('2');
expect(json).toHaveLength(2);
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[0]!.id }));
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[1]!.id }));

View file

@ -1,7 +1,5 @@
import crypto from 'node:crypto';
import { UsersPasswordEncryptionMethod, ConnectorType } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import {
mockSocialConnectorConfig,
@ -25,9 +23,13 @@ import {
import { clearConnectorsByTypes } from '#src/helpers/connector.js';
import { createUserByAdmin, expectRejects } from '#src/helpers/index.js';
import { createNewSocialUserWithUsernameAndPassword } from '#src/helpers/interactions.js';
import { generateUsername, generateEmail, generatePhone, generatePassword } from '#src/utils.js';
const randomString = () => crypto.randomBytes(8).toString('hex');
import {
generateUsername,
generateEmail,
generatePhone,
generatePassword,
randomString,
} from '#src/utils.js';
describe('admin console user management', () => {
beforeAll(async () => {
@ -62,8 +64,8 @@ describe('admin console user management', () => {
profile: { gender: 'neutral' },
});
const { customData, profile } = await getUser(user.id);
expect(customData).toStrictEqual({ foo: 'bar' });
expect(profile).toStrictEqual({ gender: 'neutral' });
expect({ ...customData }).toStrictEqual({ foo: 'bar' });
expect({ ...profile }).toStrictEqual({ gender: 'neutral' });
});
it('should fail when create user with conflict identifiers', async () => {
@ -76,22 +78,22 @@ describe('admin console user management', () => {
await createUserByAdmin({ username, password, primaryEmail, primaryPhone });
await expectRejects(createUserByAdmin({ username, password }), {
code: 'user.username_already_in_use',
statusCode: 422,
status: 422,
});
await expectRejects(createUserByAdmin({ primaryEmail }), {
code: 'user.email_already_in_use',
statusCode: 422,
status: 422,
});
await expectRejects(createUserByAdmin({ primaryPhone }), {
code: 'user.phone_already_in_use',
statusCode: 422,
status: 422,
});
});
it('should fail when get user by invalid id', async () => {
await expectRejects(getUser('invalid-user-id'), {
code: 'entity.not_found',
statusCode: 404,
status: 404,
});
});
@ -131,21 +133,21 @@ describe('admin console user management', () => {
};
const updatedProfile = await updateUserProfile(user.id, profile);
expect(updatedProfile).toStrictEqual(profile);
expect(updatedProfile).toMatchObject(profile);
const patchProfile = {
familyName: 'another name',
website: 'https://logto.io/',
};
const updatedProfile2 = await updateUserProfile(user.id, patchProfile);
expect(updatedProfile2).toStrictEqual({ ...profile, ...patchProfile });
expect(updatedProfile2).toMatchObject({ ...profile, ...patchProfile });
});
it('should respond 422 when no update data provided', async () => {
const user = await createUserByAdmin();
await expectRejects(updateUser(user.id, {}), {
code: 'entity.invalid_input',
statusCode: 422,
status: 422,
});
});
@ -160,17 +162,17 @@ describe('admin console user management', () => {
await expectRejects(updateUser(anotherUser.id, { username }), {
code: 'user.username_already_in_use',
statusCode: 422,
status: 422,
});
await expectRejects(updateUser(anotherUser.id, { primaryEmail }), {
code: 'user.email_already_in_use',
statusCode: 422,
status: 422,
});
await expectRejects(updateUser(anotherUser.id, { primaryPhone }), {
code: 'user.phone_already_in_use',
statusCode: 422,
status: 422,
});
});
@ -183,7 +185,7 @@ describe('admin console user management', () => {
await deleteUser(user.id);
const response = await getUser(user.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
it('should update user password successfully', async () => {
@ -297,7 +299,7 @@ describe('admin console user management', () => {
it('should return 204 if password is correct', async () => {
const user = await createUserByAdmin({ password: 'new_password' });
expect(await verifyUserPassword(user.id, 'new_password')).toHaveProperty('statusCode', 204);
expect(await verifyUserPassword(user.id, 'new_password')).toHaveProperty('status', 204);
await deleteUser(user.id);
});
@ -305,7 +307,7 @@ describe('admin console user management', () => {
const user = await createUserByAdmin({ password: 'new_password' });
await expectRejects(verifyUserPassword(user.id, 'wrong_password'), {
code: 'session.invalid_credentials',
statusCode: 422,
status: 422,
});
await deleteUser(user.id);
});
@ -314,7 +316,7 @@ describe('admin console user management', () => {
const user = await createUserByAdmin();
await expectRejects(verifyUserPassword(user.id, ''), {
code: 'guard.invalid_input',
statusCode: 400,
status: 400,
});
});
});

View file

@ -51,7 +51,7 @@ describe('application sign in experience', () => {
setApplicationSignInExperience('non-existent-application', applicationSignInExperiences),
{
code: 'entity.not_exists_with_id',
statusCode: 404,
status: 404,
}
);
});
@ -64,7 +64,7 @@ describe('application sign in experience', () => {
),
{
code: 'application.third_party_application_only',
statusCode: 422,
status: 422,
}
);
});
@ -112,7 +112,7 @@ describe('application sign in experience', () => {
await expectRejects(getApplicationSignInExperience(application.id), {
code: 'entity.not_exists_with_id',
statusCode: 404,
status: 404,
});
});
});

View file

@ -76,7 +76,7 @@ describe('assign user consent organizations to application', () => {
}),
{
code: 'entity.not_exists_with_id',
statusCode: 404,
status: 404,
}
);
});
@ -88,7 +88,7 @@ describe('assign user consent organizations to application', () => {
}),
{
code: 'entity.not_found',
statusCode: 404,
status: 404,
}
);
});
@ -104,7 +104,7 @@ describe('assign user consent organizations to application', () => {
),
{
code: 'application.third_party_application_only',
statusCode: 422,
status: 422,
}
);
});
@ -120,7 +120,7 @@ describe('assign user consent organizations to application', () => {
),
{
code: 'organization.require_membership',
statusCode: 422,
status: 422,
}
);
});

View file

@ -78,7 +78,7 @@ describe('assign user consent scopes to application', () => {
}),
{
code: 'application.third_party_application_only',
statusCode: 422,
status: 422,
}
);
});
@ -90,7 +90,7 @@ describe('assign user consent scopes to application', () => {
}),
{
code: 'application.user_consent_scopes_not_found',
statusCode: 422,
status: 422,
}
);
});
@ -102,7 +102,7 @@ describe('assign user consent scopes to application', () => {
}),
{
code: 'application.user_consent_scopes_not_found',
statusCode: 422,
status: 422,
}
);
});
@ -130,7 +130,7 @@ describe('assign user consent scopes to application', () => {
it('should return 404 when trying to get consent scopes from non-existing application', async () => {
await expectRejects(getUserConsentScopes('non-existing-application'), {
code: 'entity.not_exists_with_id',
statusCode: 404,
status: 404,
});
});
@ -184,7 +184,7 @@ describe('assign user consent scopes to application', () => {
),
{
code: 'entity.not_exists_with_id',
statusCode: 404,
status: 404,
}
);
});
@ -198,7 +198,7 @@ describe('assign user consent scopes to application', () => {
),
{
code: 'entity.not_found',
statusCode: 404,
status: 404,
}
);
@ -210,7 +210,7 @@ describe('assign user consent scopes to application', () => {
),
{
code: 'entity.not_found',
statusCode: 404,
status: 404,
}
);
@ -222,7 +222,7 @@ describe('assign user consent scopes to application', () => {
),
{
code: 'entity.not_found',
statusCode: 404,
status: 404,
}
);
});

View file

@ -1,6 +1,6 @@
import { ApplicationType, RoleType } from '@logto/schemas';
import { generateStandardId } from '@logto/shared';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import {
createApplication,
@ -27,7 +27,7 @@ describe('admin console application management (roles)', () => {
const application = await createApplication(generateStandardId(), applicationType);
const response = await getApplicationRoles(application.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 422).toBe(true);
expect(response instanceof HTTPError && response.response.status === 422).toBe(true);
});
it('should assign roles to app and get list successfully', async () => {
@ -59,7 +59,7 @@ describe('admin console application management (roles)', () => {
await assignRolesToApplication(application.id, [role.id]);
await expectRejects(assignRolesToApplication(application.id, [role.id]), {
code: 'application.role_exists',
statusCode: 422,
status: 422,
});
});
@ -70,7 +70,7 @@ describe('admin console application management (roles)', () => {
await expectRejects(assignRolesToApplication(application.id, [role.id]), {
code: 'application.invalid_type',
statusCode: 422,
status: 422,
});
});
@ -114,7 +114,7 @@ describe('admin console application management (roles)', () => {
const response = await deleteRoleFromApplication(application.id, role.id).catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
// This case tests GET operation on applications and filter by `types` parameter and `search` parameter.

View file

@ -1,5 +1,5 @@
import { ApplicationType } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import {
createApplication,
@ -31,7 +31,7 @@ describe('admin console application', () => {
createApplication('test-create-app', ApplicationType.Native, {
isThirdParty: true,
}),
{ code: 'application.invalid_third_party_application_type', statusCode: 400 }
{ code: 'application.invalid_third_party_application_type', status: 400 }
);
});
@ -86,7 +86,7 @@ describe('admin console application', () => {
}),
{
code: 'application.protected_application_subdomain_exists',
statusCode: 422,
status: 422,
}
);
await deleteApplication(application.id);
@ -95,7 +95,7 @@ describe('admin console application', () => {
it('should throw error when creating a protected application with invalid type', async () => {
await expectRejects(createApplication('test-create-app', ApplicationType.Protected), {
code: 'application.protected_app_metadata_is_required',
statusCode: 400,
status: 400,
});
});
@ -120,7 +120,7 @@ describe('admin console application', () => {
expect(updatedApplication.description).toBe(newApplicationDescription);
expect(updatedApplication.oidcClientMetadata.redirectUris).toEqual(newRedirectUris);
expect(updatedApplication.customClientMetadata).toStrictEqual({
expect({ ...updatedApplication.customClientMetadata }).toStrictEqual({
rotateRefreshToken: true,
refreshTokenTtlInDays: 10,
});
@ -256,6 +256,6 @@ describe('admin console application', () => {
await deleteApplication(application.id);
const response = await getApplication(application.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
});

View file

@ -1,19 +1,20 @@
import { got } from 'got';
import ky from 'ky';
import { logtoConsoleUrl } from '#src/constants.js';
describe('social connector form post callback', () => {
const request = got.extend({
const request = ky.extend({
prefixUrl: new URL(logtoConsoleUrl),
});
it('should redirect to the same path with query string', async () => {
const response = await request.post('callback/some_connector_id', {
json: { some: 'data' },
followRedirect: false,
redirect: 'manual',
throwHttpErrors: false,
});
expect(response.statusCode).toBe(303);
expect(response.headers.location).toBe('/callback/some_connector_id?some=data');
expect(response.status).toBe(303);
expect(response.headers.get('location')).toBe('/callback/some_connector_id?some=data');
});
});

View file

@ -1,4 +1,4 @@
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import {
mockEmailConnectorConfig,
@ -160,7 +160,7 @@ test('create connector with non-exist connectorId', async () => {
await cleanUpConnectorTable();
await expectRejects(postConnector({ connectorId: 'non-exist-id' }), {
code: 'connector.not_found_with_connector_id',
statusCode: 422,
status: 422,
});
});
@ -170,7 +170,7 @@ test('create non standard social connector with target', async () => {
postConnector({ connectorId: mockSocialConnectorId, metadata: { target: 'target' } }),
{
code: 'connector.cannot_overwrite_metadata_for_non_standard_connector',
statusCode: 400,
status: 400,
}
);
});
@ -180,7 +180,7 @@ test('create duplicated social connector', async () => {
await postConnector({ connectorId: mockSocialConnectorId });
await expectRejects(postConnector({ connectorId: mockSocialConnectorId }), {
code: 'connector.multiple_instances_not_supported',
statusCode: 422,
status: 422,
});
});
@ -189,7 +189,7 @@ test('override metadata for non-standard social connector', async () => {
const { id } = await postConnector({ connectorId: mockSocialConnectorId });
await expectRejects(updateConnectorConfig(id, {}, { target: 'target' }), {
code: 'connector.cannot_overwrite_metadata_for_non_standard_connector',
statusCode: 400,
status: 400,
});
});

View file

@ -1,4 +1,4 @@
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import {
listCustomPhrases,

View file

@ -19,15 +19,15 @@ describe('admin console dashboard', () => {
it('non authorized request should return 401', async () => {
await expectRejects(api.get('dashboard/users/total'), {
code: 'auth.authorization_header_missing',
statusCode: 401,
status: 401,
});
await expectRejects(api.get('dashboard/users/new'), {
code: 'auth.authorization_header_missing',
statusCode: 401,
status: 401,
});
await expectRejects(api.get('dashboard/users/active'), {
code: 'auth.authorization_header_missing',
statusCode: 401,
status: 401,
});
});

View file

@ -1,4 +1,4 @@
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { createDomain, deleteDomain, getDomain, getDomains } from '#src/api/domain.js';
import { generateDomain } from '#src/utils.js';
@ -27,7 +27,7 @@ describe('domains', () => {
await createDomain();
const response = await createDomain().catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(422);
expect(response instanceof HTTPError && response.response.status).toBe(422);
});
it('should get domain detail successfully', async () => {
@ -40,7 +40,7 @@ describe('domains', () => {
it('should return 404 if domain does not exist', async () => {
const response = await getDomain('non_existent_domain').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should delete domain successfully', async () => {
@ -49,6 +49,6 @@ describe('domains', () => {
await deleteDomain(domain.id);
const response = await getDomain(domain.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
});

View file

@ -2,6 +2,6 @@ import { api } from '#src/api/index.js';
describe('health check', () => {
it('should have a health state', async () => {
expect(await api.get('status')).toHaveProperty('statusCode', 204);
expect(await api.get('status')).toHaveProperty('status', 204);
});
});

View file

@ -19,10 +19,10 @@ describe('hooks', () => {
.patch(`hooks/${created.id}`, { json: { events: [HookEvent.PostSignIn] } })
.json<Hook>()
).toMatchObject({ ...created, events: [HookEvent.PostSignIn] });
expect(await authedAdminApi.delete(`hooks/${created.id}`)).toHaveProperty('statusCode', 204);
expect(await authedAdminApi.delete(`hooks/${created.id}`)).toHaveProperty('status', 204);
await expectRejects(authedAdminApi.get(`hooks/${created.id}`), {
code: 'entity.not_exists_with_id',
statusCode: 404,
status: 404,
});
});
@ -48,10 +48,10 @@ describe('hooks', () => {
...created,
event: HookEvent.PostSignIn,
});
expect(await authedAdminApi.delete(`hooks/${created.id}`)).toHaveProperty('statusCode', 204);
expect(await authedAdminApi.delete(`hooks/${created.id}`)).toHaveProperty('status', 204);
await expectRejects(authedAdminApi.get(`hooks/${created.id}`), {
code: 'entity.not_exists_with_id',
statusCode: 404,
status: 404,
});
});
@ -61,8 +61,8 @@ describe('hooks', () => {
const response = await authedAdminApi.get('hooks?page=1&page_size=20');
expect(response.statusCode).toBe(200);
expect(response.headers).toHaveProperty('total-number');
expect(response.status).toBe(200);
expect(response.headers.get('total-number')).toEqual(expect.any(String));
// Clean up
await authedAdminApi.delete(`hooks/${created.id}`);
@ -78,7 +78,7 @@ describe('hooks', () => {
};
await expectRejects(authedAdminApi.post('hooks', { json: payload }), {
code: 'guard.invalid_input',
statusCode: 400,
status: 400,
});
});
@ -91,7 +91,7 @@ describe('hooks', () => {
};
await expectRejects(authedAdminApi.post('hooks', { json: payload }), {
code: 'hook.missing_events',
statusCode: 400,
status: 400,
});
});
@ -102,14 +102,14 @@ describe('hooks', () => {
await expectRejects(authedAdminApi.patch('hooks/invalid_id', { json: payload }), {
code: 'entity.not_exists',
statusCode: 404,
status: 404,
});
});
it('should throw error if regenerate a hook signing key with a invalid hook id', async () => {
await expectRejects(authedAdminApi.patch('hooks/invalid_id/signing-key'), {
code: 'entity.not_exists',
statusCode: 404,
status: 404,
});
});
});

View file

@ -37,7 +37,7 @@ describe('hook testing', () => {
const response = await authedAdminApi.post(`hooks/${created.id}/test`, {
json: { events: [HookEvent.PostSignIn], config: { url: responseSuccessEndpoint } },
});
expect(response.statusCode).toBe(204);
expect(response.status).toBe(204);
// Clean Up
await authedAdminApi.delete(`hooks/${created.id}`);
@ -51,7 +51,7 @@ describe('hook testing', () => {
}),
{
code: 'entity.not_exists_with_id',
statusCode: 404,
status: 404,
}
);
});
@ -65,7 +65,7 @@ describe('hook testing', () => {
}),
{
code: 'hook.send_test_payload_failed',
statusCode: 422,
status: 422,
}
);
@ -82,7 +82,7 @@ describe('hook testing', () => {
}),
{
code: 'hook.endpoint_responded_with_error',
statusCode: 422,
status: 422,
}
);

View file

@ -30,7 +30,7 @@ describe('Interaction details guard checking', () => {
}),
{
code: 'session.not_found',
statusCode: 400,
status: 400,
}
);
});
@ -38,7 +38,7 @@ describe('Interaction details guard checking', () => {
it('DELETE /interaction', async () => {
await expectRejects(client.send(deleteInteraction), {
code: 'session.not_found',
statusCode: 400,
status: 400,
});
});
@ -49,7 +49,7 @@ describe('Interaction details guard checking', () => {
}),
{
code: 'session.not_found',
statusCode: 400,
status: 400,
}
);
});
@ -62,7 +62,7 @@ describe('Interaction details guard checking', () => {
}),
{
code: 'session.not_found',
statusCode: 400,
status: 400,
}
);
});
@ -75,7 +75,7 @@ describe('Interaction details guard checking', () => {
}),
{
code: 'session.not_found',
statusCode: 400,
status: 400,
}
);
});
@ -88,7 +88,7 @@ describe('Interaction details guard checking', () => {
}),
{
code: 'session.not_found',
statusCode: 400,
status: 400,
}
);
});
@ -96,14 +96,14 @@ describe('Interaction details guard checking', () => {
it('DELETE /interaction/profile', async () => {
await expectRejects(client.send(deleteInteractionProfile), {
code: 'session.not_found',
statusCode: 400,
status: 400,
});
});
it('POST /interaction/submit', async () => {
await expectRejects(client.submitInteraction(), {
code: 'session.not_found',
statusCode: 400,
status: 400,
});
});
@ -116,7 +116,7 @@ describe('Interaction details guard checking', () => {
}),
{
code: 'session.not_found',
statusCode: 400,
status: 400,
}
);
});
@ -128,7 +128,7 @@ describe('Interaction details guard checking', () => {
}),
{
code: 'session.not_found',
statusCode: 400,
status: 400,
}
);
});

View file

@ -26,7 +26,7 @@ describe('Interaction details results checking', () => {
}),
{
code: 'session.verification_session_not_found',
statusCode: 404,
status: 404,
}
);
});
@ -41,7 +41,7 @@ describe('Interaction details results checking', () => {
}),
{
code: 'session.verification_session_not_found',
statusCode: 404,
status: 404,
}
);
});
@ -55,7 +55,7 @@ describe('Interaction details results checking', () => {
}),
{
code: 'session.verification_session_not_found',
statusCode: 404,
status: 404,
}
);
});
@ -69,7 +69,7 @@ describe('Interaction details results checking', () => {
}),
{
code: 'session.verification_session_not_found',
statusCode: 404,
status: 404,
}
);
});
@ -78,7 +78,7 @@ describe('Interaction details results checking', () => {
const client = await initClient();
await expectRejects(client.send(deleteInteractionProfile), {
code: 'session.verification_session_not_found',
statusCode: 404,
status: 404,
});
});
@ -86,7 +86,7 @@ describe('Interaction details results checking', () => {
const client = await initClient();
await expectRejects(client.submitInteraction(), {
code: 'session.verification_session_not_found',
statusCode: 404,
status: 404,
});
});
@ -100,7 +100,7 @@ describe('Interaction details results checking', () => {
}),
{
code: 'session.verification_session_not_found',
statusCode: 404,
status: 404,
}
);
});
@ -113,7 +113,7 @@ describe('Interaction details results checking', () => {
}),
{
code: 'session.verification_session_not_found',
statusCode: 404,
status: 404,
}
);
});

View file

@ -31,7 +31,7 @@ describe('PATCH /interaction/identifiers', () => {
}),
{
code: 'user.suspended',
statusCode: 401,
status: 401,
}
);
});

View file

@ -1,10 +1,15 @@
import { InteractionEvent } from '@logto/schemas';
import { ConnectorType, InteractionEvent } from '@logto/schemas';
import { putInteraction, sendVerificationCode } from '#src/api/interaction.js';
import { initClient } from '#src/helpers/client.js';
import { clearConnectorsByTypes } from '#src/helpers/connector.js';
import { expectRejects } from '#src/helpers/index.js';
import { generateEmail, generatePhone } from '#src/utils.js';
beforeAll(async () => {
await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]);
});
/**
* Note: These test cases are designed to cover exceptional scenarios of API calls that
* cannot be covered within the auth flow.
@ -23,7 +28,7 @@ describe('POST /interaction/verification/verification-code', () => {
}),
{
code: 'connector.not_found',
statusCode: 501,
status: 501,
}
);
});
@ -41,7 +46,7 @@ describe('POST /interaction/verification/verification-code', () => {
}),
{
code: 'connector.not_found',
statusCode: 501,
status: 501,
}
);
});

View file

@ -27,7 +27,7 @@ describe('PUT /interaction/event', () => {
}),
{
code: 'auth.forbidden',
statusCode: 403,
status: 403,
}
);
@ -41,7 +41,7 @@ describe('PUT /interaction/event', () => {
}),
{
code: 'auth.forbidden',
statusCode: 403,
status: 403,
}
);
@ -65,7 +65,7 @@ describe('PUT /interaction/event', () => {
}),
{
code: 'session.interaction_not_found',
statusCode: 404,
status: 404,
}
);
@ -75,7 +75,7 @@ describe('PUT /interaction/event', () => {
}),
{
code: 'session.interaction_not_found',
statusCode: 404,
status: 404,
}
);
});
@ -96,7 +96,7 @@ describe('PUT /interaction/event', () => {
}),
{
code: 'session.interaction_not_found',
statusCode: 404,
status: 404,
}
);
@ -111,7 +111,7 @@ describe('PUT /interaction/event', () => {
}),
{
code: 'session.interaction_not_found',
statusCode: 404,
status: 404,
}
);
});

View file

@ -54,7 +54,7 @@ describe('PUT /interaction', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -69,7 +69,7 @@ describe('PUT /interaction', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -100,7 +100,7 @@ describe('PUT /interaction', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -114,7 +114,7 @@ describe('PUT /interaction', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -140,7 +140,7 @@ describe('PUT /interaction', () => {
}),
{
code: 'verification_code.not_found',
statusCode: 400,
status: 400,
}
);
@ -155,7 +155,7 @@ describe('PUT /interaction', () => {
}),
{
code: 'verification_code.not_found',
statusCode: 400,
status: 400,
}
);
@ -177,7 +177,7 @@ describe('PUT /interaction', () => {
}),
{
code: 'session.invalid_connector_id',
statusCode: 422,
status: 422,
}
);
});
@ -194,7 +194,7 @@ describe('PUT /interaction', () => {
}),
{
code: 'session.connector_session_not_found',
statusCode: 400,
status: 400,
}
);
});

View file

@ -64,14 +64,14 @@ describe('reset password', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.new_password_required_in_profile',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionProfile, { password: userProfile.password });
await expectRejects(client.submitInteraction(), {
code: 'user.same_password',
statusCode: 422,
status: 422,
});
const newPasswordRecord = generatePassword();
@ -124,14 +124,14 @@ describe('reset password', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.new_password_required_in_profile',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionProfile, { password: userProfile.password });
await expectRejects(client.submitInteraction(), {
code: 'user.same_password',
statusCode: 422,
status: 422,
});
const newPasswordRecord = generatePassword();

View file

@ -40,7 +40,7 @@ describe('reset password flow sad path', () => {
await client.successSend(putInteractionProfile, { password: generatePassword() });
await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist',
statusCode: 404,
status: 404,
});
// Clear
@ -68,7 +68,7 @@ describe('reset password flow sad path', () => {
await client.successSend(putInteractionProfile, { password: generatePassword() });
await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist',
statusCode: 404,
status: 404,
});
// Clear
@ -104,7 +104,7 @@ describe('reset password flow sad path', () => {
await client.successSend(putInteractionProfile, { password: generatePassword() });
await expectRejects(client.submitInteraction(), {
code: 'user.suspended',
statusCode: 401,
status: 401,
});
// Clear

View file

@ -39,7 +39,7 @@ const registerWithMfa = async () => {
const { codes } = await expectRejects<{ codes: string[] }>(client.submitInteraction(), {
code: 'session.mfa.backup_code_required',
statusCode: 400,
status: 400,
});
await client.send(postInteractionBindMfa, {
@ -81,7 +81,7 @@ describe('sign in and verify mfa (Backup Code)', () => {
await expectRejects(client.submitInteraction(), {
code: 'session.mfa.require_mfa_verification',
statusCode: 403,
status: 403,
});
await deleteUser(id);

View file

@ -70,7 +70,7 @@ describe('register with mfa (mandatory TOTP)', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_mfa',
statusCode: 422,
status: 422,
});
});
@ -94,7 +94,7 @@ describe('register with mfa (mandatory TOTP)', () => {
}),
{
code: 'session.mfa.invalid_totp_code',
statusCode: 400,
status: 400,
}
);
});
@ -151,7 +151,7 @@ describe('sign in and fulfill mfa (mandatory TOTP)', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_mfa',
statusCode: 422,
status: 422,
});
await deleteUser(user.id);
@ -187,7 +187,7 @@ describe('sign in and fulfill mfa (user-controlled TOTP)', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_mfa',
statusCode: 422,
status: 422,
});
await deleteUser(user.id);
@ -207,7 +207,7 @@ describe('sign in and fulfill mfa (user-controlled TOTP)', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_mfa',
statusCode: 422,
status: 422,
});
await client.successSend(skipMfaBinding);
@ -241,7 +241,7 @@ describe('sign in and verify mfa (TOTP)', () => {
await expectRejects(client.submitInteraction(), {
code: 'session.mfa.require_mfa_verification',
statusCode: 403,
status: 403,
});
await deleteUser(id);

View file

@ -146,7 +146,7 @@ describe('register with passwordless identifier', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile',
statusCode: 422,
status: 422,
});
await client.successSend(patchInteractionProfile, {
@ -251,7 +251,7 @@ describe('register with passwordless identifier', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile',
statusCode: 422,
status: 422,
});
await client.successSend(patchInteractionProfile, {
@ -322,7 +322,7 @@ describe('register with passwordless identifier', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.email_already_in_use',
statusCode: 422,
status: 422,
});
await client.successSend(deleteInteractionProfile);
@ -377,7 +377,7 @@ describe('register with passwordless identifier', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.phone_already_in_use',
statusCode: 422,
status: 422,
});
await client.successSend(deleteInteractionProfile);

View file

@ -40,7 +40,7 @@ describe('Register with identifiers sad path', () => {
}),
{
code: 'auth.forbidden',
statusCode: 403,
status: 403,
}
);
@ -67,7 +67,7 @@ describe('Register with identifiers sad path', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
});
@ -94,7 +94,7 @@ describe('Register with identifiers sad path', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -125,7 +125,7 @@ describe('Register with identifiers sad path', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);

View file

@ -133,7 +133,7 @@ describe('test register with email with SSO feature', () => {
}),
{
code: 'session.sso_enabled',
statusCode: 422,
status: 422,
}
);
});

View file

@ -127,7 +127,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist',
statusCode: 404,
status: 404,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -169,7 +169,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist',
statusCode: 404,
status: 404,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -214,7 +214,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile',
statusCode: 422,
status: 422,
});
// Fulfill user profile
@ -276,7 +276,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile',
statusCode: 422,
status: 422,
});
// Fulfill user profile with existing password
@ -287,7 +287,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.password_exists_in_profile',
statusCode: 400,
status: 400,
});
await client.successSend(putInteractionProfile, {
@ -333,7 +333,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile',
statusCode: 422,
status: 422,
});
// Fulfill user profile with existing password
@ -344,7 +344,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.username_already_in_use',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionProfile, {

View file

@ -43,7 +43,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
}),
{
code: 'auth.forbidden',
statusCode: 403,
status: 403,
}
);
@ -81,7 +81,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -99,7 +99,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -130,7 +130,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
}),
{
code: 'verification_code.code_mismatch',
statusCode: 400,
status: 400,
}
);
@ -141,7 +141,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
}),
{
code: 'verification_code.email_mismatch',
statusCode: 400,
status: 400,
}
);
});
@ -169,7 +169,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
}),
{
code: 'verification_code.code_mismatch',
statusCode: 400,
status: 400,
}
);
@ -180,7 +180,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
}),
{
code: 'verification_code.phone_mismatch',
statusCode: 400,
status: 400,
}
);
});
@ -207,7 +207,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist',
statusCode: 404,
status: 404,
});
});
@ -233,7 +233,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist',
statusCode: 404,
status: 404,
});
});
@ -263,7 +263,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.suspended',
statusCode: 401,
status: 401,
});
});
});

View file

@ -100,7 +100,7 @@ describe('test sign-in with email passcode identifier with SSO feature', () => {
}),
{
code: 'session.sso_enabled',
statusCode: 422,
status: 422,
}
);

View file

@ -161,7 +161,7 @@ describe('Sign-in flow using password identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile',
statusCode: 422,
status: 422,
});
await client.successSend(sendVerificationCode, {
@ -223,7 +223,7 @@ describe('Sign-in flow using password identifiers', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile',
statusCode: 422,
status: 422,
});
await client.successSend(sendVerificationCode, {

View file

@ -35,7 +35,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'auth.forbidden',
statusCode: 403,
status: 403,
}
);
@ -51,7 +51,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'auth.forbidden',
statusCode: 403,
status: 403,
}
);
@ -67,7 +67,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'auth.forbidden',
statusCode: 403,
status: 403,
}
);
@ -91,7 +91,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -107,7 +107,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -123,7 +123,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'user.sign_in_method_not_enabled',
statusCode: 422,
status: 422,
}
);
@ -141,7 +141,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'session.invalid_credentials',
statusCode: 422,
status: 422,
}
);
});
@ -159,7 +159,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'session.invalid_credentials',
statusCode: 422,
status: 422,
}
);
});
@ -177,7 +177,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'session.invalid_credentials',
statusCode: 422,
status: 422,
}
);
});
@ -199,7 +199,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}),
{
code: 'user.suspended',
statusCode: 401,
status: 401,
}
);
});

View file

@ -99,7 +99,7 @@ describe('test sign-in with email passcode identifier with SSO feature', () => {
}),
{
code: 'session.sso_enabled',
statusCode: 422,
status: 422,
}
);
await deleteUser(user.id);

View file

@ -6,18 +6,20 @@ import { updateSignInExperience } from '#src/api/sign-in-experience.js';
import { createSsoConnector, deleteSsoConnectorById } from '#src/api/sso-connector.js';
import { logtoUrl } from '#src/constants.js';
import { initClient } from '#src/helpers/client.js';
import { randomString } from '#src/utils.js';
describe('Single Sign On Happy Path', () => {
const connectorIdMap = new Map<string, SsoConnectorMetadata>();
const state = 'foo_state';
const redirectUri = 'http://foo.dev/callback';
const domain = `foo${randomString()}.com`;
beforeAll(async () => {
const { id, connectorName } = await createSsoConnector({
providerName: SsoProviderName.OIDC,
connectorName: 'test-oidc',
domains: ['foo.com'],
connectorName: `test-oidc-${randomString()}`,
domains: [domain],
config: {
clientId: 'foo',
clientSecret: 'bar',
@ -60,7 +62,7 @@ describe('Single Sign On Happy Path', () => {
const client = await initClient();
const response = await client.send(getSsoConnectorsByEmail, {
email: 'bar@foo.com',
email: 'bar@' + domain,
});
expect(response.length).toBeGreaterThan(0);
@ -88,7 +90,7 @@ describe('Single Sign On Happy Path', () => {
});
const response = await client.send(getSsoConnectorsByEmail, {
email: 'bar@foo.com',
email: 'bar@' + domain,
});
expect(response.length).toBe(0);

View file

@ -9,6 +9,7 @@ import { putInteraction } from '#src/api/interaction.js';
import { createSsoConnector, deleteSsoConnectorById } from '#src/api/sso-connector.js';
import { initClient } from '#src/helpers/client.js';
import { expectRejects } from '#src/helpers/index.js';
import { randomString } from '#src/utils.js';
describe('Single Sign On Sad Path', () => {
const state = 'foo_state';
@ -34,7 +35,7 @@ describe('Single Sign On Sad Path', () => {
it('should throw if connector config is invalid', async () => {
const { id } = await createSsoConnector({
providerName: SsoProviderName.OIDC,
connectorName: 'test-oidc',
connectorName: `test-oidc-${randomString()}`,
});
const client = await initClient();
@ -82,7 +83,7 @@ describe('Single Sign On Sad Path', () => {
postSamlAssertion({ connectorId, RelayState: 'foo', SAMLResponse: samlAssertion }),
{
code: 'session.not_found',
statusCode: 400,
status: 400,
}
);
});
@ -103,7 +104,7 @@ describe('Single Sign On Sad Path', () => {
postSamlAssertion({ connectorId, RelayState, SAMLResponse: samlAssertion }),
{
code: 'connector.authorization_failed',
statusCode: 401,
status: 401,
}
);
});

View file

@ -47,7 +47,7 @@ describe('social sign-in', () => {
await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email, ConnectorType.Sms]);
});
describe.only('register and sign-in', () => {
describe('register and sign-in', () => {
const socialUserId = generateUserId();
it('register with social', async () => {
@ -68,7 +68,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -124,7 +124,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -137,7 +137,7 @@ describe('social sign-in', () => {
const { primaryEmail, identities } = await getUser(uid);
expect(primaryEmail).toBe(socialEmail);
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
expect(identities[mockSocialConnectorTarget]).toStrictEqual({
expect(identities[mockSocialConnectorTarget]).toMatchObject({
details: {
email: expect.any(String),
id: expect.any(String),
@ -176,7 +176,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -213,7 +213,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -255,7 +255,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(patchInteractionIdentifiers, {
@ -319,7 +319,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -327,7 +327,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile',
statusCode: 422,
status: 422,
});
await client.successSend(patchInteractionProfile, { username: generateUsername() });
@ -363,7 +363,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -400,7 +400,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });

View file

@ -73,7 +73,7 @@ describe('Social identifier interaction sad path', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -101,7 +101,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'session.insufficient_info',
statusCode: 400,
status: 400,
}
);
});
@ -121,7 +121,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'guard.invalid_input',
statusCode: 400,
status: 400,
}
);
});
@ -141,7 +141,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'connector.unexpected_type',
statusCode: 400,
status: 400,
}
);
@ -154,7 +154,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'connector.unexpected_type',
statusCode: 400,
status: 400,
}
);
});
@ -173,7 +173,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'entity.not_found',
statusCode: 404,
status: 404,
}
);
});
@ -195,7 +195,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'session.invalid_connector_id',
statusCode: 422,
status: 422,
}
);
});
@ -217,7 +217,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'session.invalid_connector_id',
statusCode: 422,
status: 422,
}
);
});
@ -240,7 +240,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'session.connector_session_not_found',
statusCode: 400,
status: 400,
}
);
@ -252,7 +252,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'session.connector_session_not_found',
statusCode: 400,
status: 400,
}
);
});
@ -281,7 +281,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'session.connector_session_not_found',
statusCode: 400,
status: 400,
}
);
@ -293,7 +293,7 @@ describe('Social identifier interaction sad path', () => {
}),
{
code: 'session.connector_session_not_found',
statusCode: 400,
status: 400,
}
);
});
@ -318,7 +318,7 @@ describe('Social identifier interaction sad path', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.suspended',
statusCode: 401,
status: 401,
});
// Reset
@ -350,7 +350,7 @@ describe('Social identifier interaction sad path', () => {
await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist',
statusCode: 422,
status: 422,
});
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -361,7 +361,7 @@ describe('Social identifier interaction sad path', () => {
await expectRejects(client.submitInteraction(), {
code: 'session.connector_session_not_found',
statusCode: 404,
status: 404,
});
});
});

View file

@ -22,7 +22,7 @@ describe('logs', () => {
it('should throw on getting non-exist log detail', async () => {
await expectRejects(getLog('non-exist-log-id'), {
code: 'entity.not_exists_with_id',
statusCode: 404,
status: 404,
});
});
});

View file

@ -61,14 +61,14 @@ describe('admin console sign-in experience', () => {
expect(privateKeys).toHaveLength(1);
await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.PrivateKeys, privateKeys[0]!.id), {
code: 'oidc.key_required',
statusCode: 422,
status: 422,
});
const cookieKeys = await getOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
expect(cookieKeys).toHaveLength(1);
await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.CookieKeys, cookieKeys[0]!.id), {
code: 'oidc.key_required',
statusCode: 422,
status: 422,
});
});
@ -141,11 +141,11 @@ describe('admin console sign-in experience', () => {
await expectRejects(getJwtCustomizer('access-token'), {
code: 'entity.not_exists',
statusCode: 404,
status: 404,
});
await expectRejects(deleteJwtCustomizer('access-token'), {
code: 'entity.not_found',
statusCode: 404,
status: 404,
});
const accessToken = await upsertJwtCustomizer('access-token', accessTokenJwtCustomizerPayload);
expect(accessToken).toMatchObject(accessTokenJwtCustomizerPayload);
@ -164,7 +164,7 @@ describe('admin console sign-in experience', () => {
await expect(deleteJwtCustomizer('access-token')).resolves.not.toThrow();
await expectRejects(getJwtCustomizer('access-token'), {
code: 'entity.not_exists',
statusCode: 404,
status: 404,
});
});
@ -177,11 +177,11 @@ describe('admin console sign-in experience', () => {
await expectRejects(getJwtCustomizer('client-credentials'), {
code: 'entity.not_exists',
statusCode: 404,
status: 404,
});
await expectRejects(deleteJwtCustomizer('client-credentials'), {
code: 'entity.not_found',
statusCode: 404,
status: 404,
});
const clientCredentials = await upsertJwtCustomizer(
'client-credentials',
@ -203,7 +203,7 @@ describe('admin console sign-in experience', () => {
await expect(deleteJwtCustomizer('client-credentials')).resolves.not.toThrow();
await expectRejects(getJwtCustomizer('client-credentials'), {
code: 'entity.not_exists',
statusCode: 404,
status: 404,
});
});
});

View file

@ -1,4 +1,4 @@
import { got } from 'got';
import ky from 'ky';
import { logtoConsoleUrl, logtoUrl } from '#src/constants.js';
import {
@ -11,35 +11,35 @@ import { expectRejects } from '#src/helpers/index.js';
describe('me', () => {
it('should only be available in admin tenant', async () => {
await expectRejects(got.get(new URL('/me/custom-data', logtoConsoleUrl)), {
await expectRejects(ky.get(new URL('/me/custom-data', logtoConsoleUrl)), {
code: 'auth.authorization_header_missing',
statusCode: 401,
status: 401,
});
// Redirect to UI
const response = await got.get(new URL('/me/custom-data', logtoUrl));
expect(response.statusCode).toBe(200);
expect(response.headers['content-type']?.startsWith('text/html;')).toBeTruthy();
const response = await ky.get(new URL('/me/custom-data', logtoUrl));
expect(response.status).toBe(200);
expect(response.headers.get('content-type')?.startsWith('text/html;')).toBeTruthy();
});
it('should only recognize the access token with correct resource and scope', async () => {
const { id, client } = await createUserWithAllRolesAndSignInToClient();
await expectRejects(
got.get(logtoConsoleUrl + '/me/custom-data', {
ky.get(logtoConsoleUrl + '/me/custom-data', {
headers: { authorization: `Bearer ${await client.getAccessToken(resourceDefault)}` },
}),
{
code: 'auth.unauthorized',
statusCode: 401,
status: 401,
}
);
await expect(
got.get(logtoConsoleUrl + '/me/custom-data', {
ky.get(logtoConsoleUrl + '/me/custom-data', {
headers: { authorization: `Bearer ${await client.getAccessToken(resourceMe)}` },
})
).resolves.toHaveProperty('statusCode', 200);
).resolves.toHaveProperty('status', 200);
await deleteUser(id);
});
@ -48,14 +48,14 @@ describe('me', () => {
const { id, client } = await createUserWithAllRolesAndSignInToClient();
const headers = { authorization: `Bearer ${await client.getAccessToken(resourceMe)}` };
const data = await got
const data = await ky
.get(logtoConsoleUrl + '/me/custom-data', { headers })
.json<Record<string, unknown>>();
const newData = await got
const newData = await ky
.patch(logtoConsoleUrl + '/me/custom-data', { headers, json: { foo: 'bar' } })
.json();
.json<Record<string, unknown>>();
expect({ ...data, foo: 'bar' }).toStrictEqual(newData);
expect({ ...data, foo: 'bar' }).toStrictEqual({ ...newData });
await deleteUser(id);
});

View file

@ -1,32 +1,30 @@
import { demoAppApplicationId } from '@logto/schemas';
import { trySafe } from '@silverhand/essentials';
import { HTTPError, type Headers, got } from 'got';
import ky, { HTTPError } from 'ky';
import { type KyHeadersInit } from 'node_modules/ky/distribution/types/options.js';
import { logtoUrl } from '#src/constants.js';
describe('content-type: application/json compatibility', () => {
const api = got.extend({
const api = ky.extend({
prefixUrl: new URL('/oidc', logtoUrl),
});
const expectErrorMessageForPayload = async (
payload: Record<string, unknown>,
errorMessage: string,
headers: Headers = {}
headers: KyHeadersInit = {}
) => {
return trySafe(
api.post('token', {
headers,
json: payload,
}),
(error) => {
async (error) => {
if (!(error instanceof HTTPError)) {
throw new TypeError('Error is not a HTTPError instance.');
}
expect(JSON.parse(String(error.response.body))).toHaveProperty(
'error_description',
errorMessage
);
expect(await error.response.json()).toHaveProperty('error_description', errorMessage);
}
);
};

View file

@ -4,7 +4,6 @@ import { fetchTokenByRefreshToken } from '@logto/js';
import { InteractionEvent, type Resource, RoleType } from '@logto/schemas';
import { assert } from '@silverhand/essentials';
import { createRemoteJWKSet, jwtVerify } from 'jose';
import fetch from 'node-fetch';
import { createResource, deleteResource, deleteUser, putInteraction } from '#src/api/index.js';
import { assignUsersToRole, createRole, deleteRole } from '#src/api/role.js';

View file

@ -4,8 +4,8 @@ import { decodeAccessToken } from '@logto/js';
import { type LogtoConfig, Prompt, PersistKey } from '@logto/node';
import { GrantType, InteractionEvent, demoAppApplicationId } from '@logto/schemas';
import { isKeyInObject, removeUndefinedKeys } from '@silverhand/essentials';
import { HTTPError, got } from 'got';
import { createRemoteJWKSet, jwtVerify } from 'jose';
import ky, { HTTPError } from 'ky';
import { putInteraction } from '#src/api/index.js';
import MockClient, { defaultConfig } from '#src/client/index.js';
@ -19,7 +19,7 @@ import { generateUsername, generatePassword } from '#src/utils.js';
/** A helper class to simplify the test on grant errors. */
class GrantError extends Error {
constructor(
public readonly statusCode: number,
public readonly status: number,
public readonly body: unknown
) {
super();
@ -27,9 +27,9 @@ class GrantError extends Error {
}
/** Create a grant error matcher that matches certain elements of the error response. */
const grantErrorContaining = (code: string, description: string, statusCode = 400) =>
const grantErrorContaining = (code: string, description: string, status = 400) =>
new GrantError(
statusCode,
status,
expect.objectContaining({
code,
error_description: description,
@ -49,15 +49,20 @@ class MockOrganizationClient extends MockClient {
async fetchOrganizationToken(organizationId?: string, scopes?: string[]) {
const refreshToken = await this.getRefreshToken();
try {
const json = await got
const json = await ky
.post(`${this.config.endpoint}/oidc/token`, {
form: removeUndefinedKeys({
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(
removeUndefinedKeys({
grant_type: GrantType.RefreshToken,
client_id: this.config.appId,
refresh_token: refreshToken,
refresh_token: refreshToken ?? undefined,
organization_id: organizationId,
scope: scopes?.join(' '),
}),
})
),
})
.json();
if (isKeyInObject(json, 'refresh_token')) {
@ -66,7 +71,9 @@ class MockOrganizationClient extends MockClient {
return json;
} catch (error) {
if (error instanceof HTTPError) {
throw new GrantError(error.response.statusCode, JSON.parse(String(error.response.body)));
const json: unknown = JSON.parse(await error.response.text());
console.error('HTTPError:', error.response.status, JSON.stringify(json, undefined, 2));
throw new GrantError(error.response.status, json);
}
throw error;
}

View file

@ -2,7 +2,7 @@ import assert from 'node:assert';
import { ConnectorType } from '@logto/connector-kit';
import { generateStandardId } from '@logto/shared';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { createUser } from '#src/api/admin-user.js';
import { clearConnectorsByTypes, setEmailConnector } from '#src/helpers/connector.js';
@ -11,12 +11,10 @@ import { OrganizationApiTest, OrganizationInvitationApiTest } from '#src/helpers
const randomId = () => generateStandardId(4);
const expectErrorResponse = (error: unknown, status: number, code: string) => {
const expectErrorResponse = async (error: unknown, statusCode: number, code: string) => {
assert(error instanceof HTTPError);
const { statusCode, body: raw } = error.response;
const body: unknown = JSON.parse(String(raw));
expect(statusCode).toBe(status);
expect(body).toMatchObject({ code });
expect(error.response.status).toBe(statusCode);
expect(await error.response.json()).toMatchObject({ code });
};
describe('organization invitation creation', () => {
@ -105,7 +103,7 @@ describe('organization invitation creation', () => {
})
.catch((error: unknown) => error);
expectErrorResponse(error, 501, 'connector.not_found');
await expectErrorResponse(error, 501, 'connector.not_found');
});
it('should not be able to create invitations with the same email', async () => {
@ -124,7 +122,7 @@ describe('organization invitation creation', () => {
})
.catch((error: unknown) => error);
expectErrorResponse(error, 422, 'entity.unique_integrity_violation');
await expectErrorResponse(error, 422, 'entity.unique_integrity_violation');
});
it('should be able to create invitations with the same email for different organizations', async () => {
@ -157,7 +155,7 @@ describe('organization invitation creation', () => {
})
.catch((error: unknown) => error);
expectErrorResponse(error, 400, 'request.invalid_input');
await expectErrorResponse(error, 400, 'request.invalid_input');
});
it('should not be able to create invitations if the invitee is already a member of the organization', async () => {
@ -174,7 +172,7 @@ describe('organization invitation creation', () => {
})
.catch((error: unknown) => error);
expectErrorResponse(error, 422, 'request.invalid_input');
await expectErrorResponse(error, 422, 'request.invalid_input');
});
it('should not be able to create invitations with an invalid email', async () => {
@ -187,7 +185,7 @@ describe('organization invitation creation', () => {
})
.catch((error: unknown) => error);
expectErrorResponse(error, 400, 'guard.invalid_input');
await expectErrorResponse(error, 400, 'guard.invalid_input');
});
it('should not be able to create invitations with an invalid organization id', async () => {
@ -199,7 +197,7 @@ describe('organization invitation creation', () => {
})
.catch((error: unknown) => error);
expectErrorResponse(error, 422, 'entity.relation_foreign_key_not_found');
await expectErrorResponse(error, 422, 'entity.relation_foreign_key_not_found');
});
it('should be able to create invitations with organization role ids', async () => {

View file

@ -2,19 +2,17 @@ import assert from 'node:assert';
import { OrganizationInvitationStatus } from '@logto/schemas';
import { generateStandardId } from '@logto/shared';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { OrganizationApiTest, OrganizationInvitationApiTest } from '#src/helpers/organization.js';
import { UserApiTest } from '#src/helpers/user.js';
const randomId = () => generateStandardId(4);
const expectErrorResponse = (error: unknown, status: number, code: string) => {
const expectErrorResponse = async (error: unknown, statusCode: number, code: string) => {
assert(error instanceof HTTPError);
const { statusCode, body: raw } = error.response;
const body: unknown = JSON.parse(String(raw));
expect(statusCode).toBe(status);
expect(body).toMatchObject({ code });
expect(error.response.status).toBe(statusCode);
expect(await error.response.json()).toMatchObject({ code });
};
describe('organization invitation status update', () => {
@ -50,7 +48,7 @@ describe('organization invitation status update', () => {
const error = await invitationApi
.updateStatus(invitation.id, OrganizationInvitationStatus.Accepted)
.catch((error: unknown) => error);
expectErrorResponse(error, 422, 'request.invalid_input');
await expectErrorResponse(error, 422, 'request.invalid_input');
});
it('should be able to accept an invitation', async () => {
@ -130,7 +128,7 @@ describe('organization invitation status update', () => {
.updateStatus(invitation.id, OrganizationInvitationStatus.Accepted, user.id)
.catch((error: unknown) => error);
expectErrorResponse(error, 422, 'request.invalid_input');
await expectErrorResponse(error, 422, 'request.invalid_input');
});
it('should not be able to accept an invitation with an invalid user id', async () => {
@ -146,7 +144,7 @@ describe('organization invitation status update', () => {
.updateStatus(invitation.id, OrganizationInvitationStatus.Accepted, 'invalid')
.catch((error: unknown) => error);
expectErrorResponse(error, 404, 'entity.not_found');
await expectErrorResponse(error, 404, 'entity.not_found');
});
it('should not be able to update the status of an ended invitation', async () => {
@ -164,6 +162,6 @@ describe('organization invitation status update', () => {
.updateStatus(invitation.id, OrganizationInvitationStatus.Accepted)
.catch((error: unknown) => error);
expectErrorResponse(error, 422, 'request.invalid_input');
await expectErrorResponse(error, 422, 'request.invalid_input');
});
});

View file

@ -2,7 +2,7 @@ import assert from 'node:assert';
import { generateStandardId } from '@logto/shared';
import { isKeyInObject, pick } from '@silverhand/essentials';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { OrganizationRoleApiTest, OrganizationScopeApiTest } from '#src/helpers/organization.js';
@ -25,9 +25,8 @@ describe('organization role APIs', () => {
assert(response instanceof HTTPError);
const { statusCode, body: raw } = response.response;
const body: unknown = JSON.parse(String(raw));
expect(statusCode).toBe(422);
const body: unknown = await response.response.json();
expect(response.response.status).toBe(422);
expect(isKeyInObject(body, 'code') && body.code).toBe('entity.unique_integrity_violation');
});
@ -97,7 +96,7 @@ describe('organization role APIs', () => {
it('should fail when try to get an organization role that does not exist', async () => {
const response = await roleApi.get('0').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should be able to update organization role', async () => {
@ -126,8 +125,8 @@ describe('organization role APIs', () => {
.catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject(
expect(response.response.status).toBe(422);
expect(await response.response.json()).toMatchObject(
expect.objectContaining({
code: 'entity.unique_integrity_violation',
})
@ -138,12 +137,12 @@ describe('organization role APIs', () => {
const createdRole = await roleApi.create({ name: 'test' + randomId() });
await roleApi.delete(createdRole.id);
const response = await roleApi.get(createdRole.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail when try to delete an organization role that does not exist', async () => {
const response = await roleApi.delete('0').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
});
@ -212,8 +211,8 @@ describe('organization role APIs', () => {
.catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject(
expect(response.response.status).toBe(422);
expect(await response.response.json()).toMatchObject(
expect.objectContaining({
code: 'entity.relation_foreign_key_not_found',
})
@ -248,7 +247,7 @@ describe('organization role APIs', () => {
const response = await roleApi.deleteScope(role.id, '0').catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(404);
expect(response.response.status).toBe(404);
});
});
});

View file

@ -2,7 +2,7 @@ import assert from 'node:assert';
import { generateStandardId } from '@logto/shared';
import { isKeyInObject } from '@silverhand/essentials';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { OrganizationScopeApiTest } from '#src/helpers/organization.js';
@ -22,9 +22,8 @@ describe('organization scope APIs', () => {
assert(response instanceof HTTPError);
const { statusCode, body: raw } = response.response;
const body: unknown = JSON.parse(String(raw));
expect(statusCode).toBe(422);
const body: unknown = await response.response.json();
expect(response.response.status).toBe(422);
expect(isKeyInObject(body, 'code') && body.code).toBe('entity.unique_integrity_violation');
});
@ -70,7 +69,7 @@ describe('organization scope APIs', () => {
it('should fail when try to get an organization scope that does not exist', async () => {
const response = await scopeApi.get('0').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should be able to update organization scope', async () => {
@ -99,8 +98,8 @@ describe('organization scope APIs', () => {
.catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject(
expect(response.response.status).toBe(422);
expect(await response.response.json()).toMatchObject(
expect.objectContaining({
code: 'entity.unique_integrity_violation',
})
@ -111,11 +110,11 @@ describe('organization scope APIs', () => {
const createdScope = await scopeApi.create({ name: 'test' + randomId() });
await scopeApi.delete(createdScope.id);
const response = await scopeApi.get(createdScope.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail when try to delete an organization scope that does not exist', async () => {
const response = await scopeApi.delete('0').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
});

View file

@ -1,6 +1,6 @@
import assert from 'node:assert';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { OrganizationApiTest } from '#src/helpers/organization.js';
import { UserApiTest } from '#src/helpers/user.js';
@ -95,14 +95,14 @@ describe('organization user APIs', () => {
const response = await organizationApi
.addUsers(organization.id, [])
.catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
expect(response instanceof HTTPError && response.response.status).toBe(400);
});
it('should fail when try to add user to an organization that does not exist', async () => {
const response = await organizationApi.addUsers('0', ['0']).catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject(
expect(response.response.status).toBe(422);
expect(await response.response.json()).toMatchObject(
expect.objectContaining({ code: 'entity.relation_foreign_key_not_found' })
);
});
@ -120,7 +120,7 @@ describe('organization user APIs', () => {
it('should fail when try to delete user from an organization that does not exist', async () => {
const response = await organizationApi.deleteUser('0', '0').catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(404);
expect(response.response.status).toBe(404);
});
});
@ -146,8 +146,8 @@ describe('organization user APIs', () => {
.catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject(
expect(response.response.status).toBe(422);
expect(await response.response.json()).toMatchObject(
expect.objectContaining({ code: 'organization.require_membership' })
);
@ -244,7 +244,7 @@ describe('organization user APIs', () => {
const response = await organizationApi
.getUserRoles(organization.id, user.id)
.catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(422); // Require membership
expect(response instanceof HTTPError && response.response.status).toBe(422); // Require membership
});
});
});

View file

@ -1,4 +1,4 @@
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { OrganizationApiTest } from '#src/helpers/organization.js';
import { UserApiTest } from '#src/helpers/user.js';
@ -95,7 +95,7 @@ describe('organization APIs', () => {
it('should fail when try to get an organization that does not exist', async () => {
const response = await organizationApi.get('0').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should be able to update organization', async () => {
@ -117,11 +117,11 @@ describe('organization APIs', () => {
const response = await organizationApi
.get(createdOrganization.id)
.catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail when try to delete an organization that does not exist', async () => {
const response = await organizationApi.delete('0').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
});

View file

@ -1,7 +1,7 @@
import assert from 'node:assert';
import { defaultManagementApi } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { createResource } from '#src/api/index.js';
import { createScope, deleteScope, getScopes, updateScope } from '#src/api/scope.js';
@ -32,28 +32,28 @@ describe('scopes', () => {
const response = await createScope(resource.id, createdScope.name).catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode === 422).toBe(true);
expect(response instanceof HTTPError && response.response.status === 422).toBe(true);
});
it('should return 404 when create scope with invalid resource id', async () => {
const response = await createScope('invalid_resource_id', 'invalid_scope_name').catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
it('should return 400 if scope name is empty', async () => {
const resource = await createResource();
const response = await createScope(resource.id, '').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 400).toBe(true);
expect(response instanceof HTTPError && response.response.status === 400).toBe(true);
});
it('should return 400 if scope name has empty space', async () => {
const resource = await createResource();
const response = await createScope(resource.id, 'scope id').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 400).toBe(true);
expect(response instanceof HTTPError && response.response.status === 400).toBe(true);
});
it('should update scope successfully', async () => {
@ -82,7 +82,7 @@ describe('scopes', () => {
const response = await updateScope(resource.id, createdScope2.id, {
name: createdScope.name,
}).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 422).toBe(true);
expect(response instanceof HTTPError && response.response.status === 422).toBe(true);
});
it('should return 400 if update scope name that has empty space', async () => {
@ -93,7 +93,7 @@ describe('scopes', () => {
}).catch((error: unknown) => error);
assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(400);
expect(response.response.status).toBe(400);
});
it('should return 404 when update scope with invalid resource id', async () => {
@ -101,7 +101,7 @@ describe('scopes', () => {
name: 'scope',
}).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
it('should return 404 when update scope with invalid scope id', async () => {
@ -111,7 +111,7 @@ describe('scopes', () => {
name: 'scope',
}).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
it('should delete scope successfully', async () => {

View file

@ -1,5 +1,5 @@
import { defaultManagementApi } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import {
createResource,
@ -21,7 +21,7 @@ describe('admin console api resources', () => {
it('should return 404 if resource not found', async () => {
const response = await getResource('not_found').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
it('should create api resource successfully', async () => {
@ -49,7 +49,7 @@ describe('admin console api resources', () => {
const resourceName2 = generateResourceName();
await expectRejects(createResource(resourceName2, resourceIndicator), {
code: 'resource.resource_identifier_in_use',
statusCode: 422,
status: 422,
});
});
@ -93,7 +93,7 @@ describe('admin console api resources', () => {
name: 123,
}).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 400).toBe(true);
expect(response instanceof HTTPError && response.response.status === 400).toBe(true);
});
it('should not update api resource indicator', async () => {
@ -119,19 +119,19 @@ describe('admin console api resources', () => {
await deleteResource(createdResource.id);
const response = await getResource(createdResource.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
it('should throw when deleting management api resource', async () => {
const response = await deleteResource(defaultManagementApi.resource.id).catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode === 400).toBe(true);
expect(response instanceof HTTPError && response.response.status === 400).toBe(true);
});
it('should throw 404 when delete api resource not found', async () => {
const response = await deleteResource('dummy_id').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
it('be able to set only one default api resource', async () => {

View file

@ -1,6 +1,6 @@
import { ApplicationType, RoleType } from '@logto/schemas';
import { generateStandardId } from '@logto/shared';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { assignRolesToApplication, createApplication } from '#src/api/index.js';
import {
@ -27,7 +27,7 @@ describe('roles applications', () => {
it('should return 404 if role not found', async () => {
const response = await getRoleApplications('not-found').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should assign applications to role successfully', async () => {
@ -52,13 +52,13 @@ describe('roles applications', () => {
it('should fail when try to assign empty applications', async () => {
const role = await createRole({ type: RoleType.MachineToMachine });
const response = await assignApplicationsToRole([], role.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
expect(response instanceof HTTPError && response.response.status).toBe(400);
});
it('should fail with invalid application input', async () => {
const role = await createRole({ type: RoleType.MachineToMachine });
const response = await assignApplicationsToRole([''], role.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
expect(response instanceof HTTPError && response.response.status).toBe(400);
});
it('should fail if role not found', async () => {
@ -66,7 +66,7 @@ describe('roles applications', () => {
const response = await assignApplicationsToRole([m2mApp.id], 'not-found').catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail if application not found', async () => {
@ -74,7 +74,7 @@ describe('roles applications', () => {
const response = await assignApplicationsToRole(['not-found'], role.id).catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should remove application from role successfully', async () => {
@ -95,7 +95,7 @@ describe('roles applications', () => {
const response = await deleteApplicationFromRole(m2mApp.id, 'not-found').catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail if application not found when trying to remove application from role', async () => {
@ -103,7 +103,7 @@ describe('roles applications', () => {
const response = await deleteApplicationFromRole('not-found', role.id).catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
// This case tests GET operation on roles and filter by `type` parameter and `search` parameter.

View file

@ -1,5 +1,5 @@
import { defaultManagementApi } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { createResource } from '#src/api/index.js';
import {
@ -24,7 +24,7 @@ describe('roles scopes', () => {
it('should return 404 if role not found', async () => {
const response = await getRoleScopes('not-found').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should return empty if role has no scopes', async () => {
@ -46,13 +46,13 @@ describe('roles scopes', () => {
it('should fail when try to assign empty scopes', async () => {
const role = await createRole({});
const response = await assignScopesToRole([], role.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
expect(response instanceof HTTPError && response.response.status).toBe(400);
});
it('should fail with invalid scope input', async () => {
const role = await createRole({});
const response = await assignScopesToRole([''], role.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
expect(response instanceof HTTPError && response.response.status).toBe(400);
});
it('should fail if role not found', async () => {
@ -61,7 +61,7 @@ describe('roles scopes', () => {
const response = await assignScopesToRole([scope.id], 'not-found').catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail if scope not found', async () => {
@ -69,7 +69,7 @@ describe('roles scopes', () => {
const response = await assignScopesToRole(['not-found'], role.id).catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail if scope already assigned to role', async () => {
@ -81,7 +81,7 @@ describe('roles scopes', () => {
const response = await assignScopesToRole([scope1.id, scope2.id], role.id).catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(422);
expect(response instanceof HTTPError && response.response.status).toBe(422);
});
it('should fail if try to assign management API scope(s) to user role', async () => {
@ -91,7 +91,7 @@ describe('roles scopes', () => {
[defaultManagementApi.scopes[0]!.id],
userRole.id
).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
expect(response instanceof HTTPError && response.response.status).toBe(400);
});
it('should remove scope from role successfully', async () => {
@ -113,7 +113,7 @@ describe('roles scopes', () => {
const resource = await createResource();
const scope = await createScope(resource.id);
const response = await deleteScopeFromRole(scope.id, role.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail when try to remove scope from role that is not found', async () => {
@ -122,7 +122,7 @@ describe('roles scopes', () => {
const response = await deleteScopeFromRole(scope.id, 'not-found').catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail when try to assign a scope to an internal role', async () => {
@ -132,6 +132,6 @@ describe('roles scopes', () => {
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(403);
expect(response instanceof HTTPError && response.response.status).toBe(403);
});
});

View file

@ -1,5 +1,5 @@
import { defaultManagementApi } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { createResource } from '#src/api/resource.js';
import {
@ -49,13 +49,13 @@ describe('roles', () => {
const { name } = await createRole({});
const response = await createRole({ name }).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(422);
expect(response instanceof HTTPError && response.response.status).toBe(422);
});
it('should fail when try to create an internal role', async () => {
const response = await createRole({ name: '#internal:foo' }).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(403);
expect(response instanceof HTTPError && response.response.status).toBe(403);
});
it('should fail when try to create role with management API scope(s)', async () => {
@ -63,7 +63,7 @@ describe('roles', () => {
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
expect(response instanceof HTTPError && response.response.status).toBe(400);
});
it('should get role detail successfully', async () => {
@ -77,7 +77,7 @@ describe('roles', () => {
it('should return 404 if role does not exist', async () => {
const response = await getRole('non_existent_role').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should update role details successfully', async () => {
@ -103,14 +103,14 @@ describe('roles', () => {
const response = await updateRole(role2.id, {
name: role1.name,
}).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(422);
expect(response instanceof HTTPError && response.response.status).toBe(422);
});
it('should fail when update a non-existent role', async () => {
const response = await updateRole('non_existent_role', {
name: 'new_name',
}).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail when try to update an internal role', async () => {
@ -120,7 +120,7 @@ describe('roles', () => {
name: '#internal:foo',
}).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(403);
expect(response instanceof HTTPError && response.response.status).toBe(403);
});
it('should delete role successfully', async () => {
@ -129,12 +129,12 @@ describe('roles', () => {
await deleteRole(role.id);
const response = await getRole(role.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should return 404 if role does not exist', async () => {
const response = await deleteRole('non_existent_role').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
});

View file

@ -1,5 +1,5 @@
import { RoleType } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import { createUser } from '#src/api/index.js';
import {
@ -28,7 +28,7 @@ describe('roles users', () => {
it('should return 404 if role not found', async () => {
const response = await getRoleUsers('not-found').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should assign users to role successfully', async () => {
@ -66,7 +66,7 @@ describe('roles users', () => {
const user = await createUser(generateNewUserProfile({}));
await expectRejects(assignUsersToRole([user.id], m2mRole.id), {
code: 'entity.db_constraint_violated',
statusCode: 422,
status: 422,
});
const users = await getRoleUsers(m2mRole.id);
@ -76,13 +76,13 @@ describe('roles users', () => {
it('should fail when try to assign empty users', async () => {
const role = await createRole({});
const response = await assignUsersToRole([], role.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
expect(response instanceof HTTPError && response.response.status).toBe(400);
});
it('should fail with invalid user input', async () => {
const role = await createRole({});
const response = await assignUsersToRole([''], role.id).catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode).toBe(400);
expect(response instanceof HTTPError && response.response.status).toBe(400);
});
it('should fail if role not found', async () => {
@ -90,7 +90,7 @@ describe('roles users', () => {
const response = await assignUsersToRole([user.id], 'not-found').catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail if user not found', async () => {
@ -98,7 +98,7 @@ describe('roles users', () => {
const response = await assignUsersToRole(['not-found'], role.id).catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should remove user from role successfully', async () => {
@ -119,7 +119,7 @@ describe('roles users', () => {
const response = await deleteUserFromRole(user.id, 'not-found').catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
it('should fail if user not found when trying to remove user from role', async () => {
@ -127,6 +127,6 @@ describe('roles users', () => {
const response = await deleteUserFromRole('not-found', role.id).catch(
(error: unknown) => error
);
expect(response instanceof HTTPError && response.response.statusCode).toBe(404);
expect(response instanceof HTTPError && response.response.status).toBe(404);
});
});

View file

@ -45,7 +45,7 @@ describe('admin console sign-in experience', () => {
await expectRejects(updateSignInExperience(newSignInExperience), {
code: 'sign_in_experiences.username_requires_password',
statusCode: 400,
status: 400,
});
});
});

View file

@ -1,5 +1,5 @@
import { SsoProviderName } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import {
providerNames,
@ -64,7 +64,7 @@ describe('post sso-connectors', () => {
providerName: 'OIDC',
connectorName: 'test connector name',
}),
{ code: 'single_sign_on.duplicate_connector_name', statusCode: 409 }
{ code: 'single_sign_on.duplicate_connector_name', status: 409 }
);
await deleteSsoConnectorById(id);
@ -202,7 +202,7 @@ describe('patch sso-connector by id', () => {
patchSsoConnectorById(id2, {
connectorName: 'test connector name',
}),
{ code: 'single_sign_on.duplicate_connector_name', statusCode: 409 }
{ code: 'single_sign_on.duplicate_connector_name', status: 409 }
);
await deleteSsoConnectorById(id);

View file

@ -9,11 +9,11 @@ const { default: OpenApiSchemaValidator } = Validator;
describe('Swagger check', () => {
it('should provide a valid swagger.json', async () => {
const response = await api.get('swagger.json');
expect(response).toHaveProperty('statusCode', 200);
expect(response.headers['content-type']).toContain('application/json');
expect(response).toHaveProperty('status', 200);
expect(response.headers.get('content-type')).toContain('application/json');
// Use multiple validators to be more confident
const object: unknown = JSON.parse(response.body);
const object: unknown = await response.json();
const validator = new OpenApiSchemaValidator({ version: 3 });
const result = validator.validate(object as OpenAPI.Document);

View file

@ -32,7 +32,7 @@ describe('Generic verification code through management API', () => {
it('should create an email verification code on server side', async () => {
const payload: RequestVerificationCodePayload = { email: mockEmail };
const response = await requestVerificationCode(payload);
expect(response.statusCode).toBe(204);
expect(response.status).toBe(204);
const { code, type, address } = await readConnectorMessage('Email');
@ -44,7 +44,7 @@ describe('Generic verification code through management API', () => {
it('should create an SMS verification code on server side', async () => {
const payload: RequestVerificationCodePayload = { phone: mockPhone };
const response = await requestVerificationCode(payload);
expect(response.statusCode).toBe(204);
expect(response.status).toBe(204);
const { code, type, phone } = await readConnectorMessage('Sms');
@ -56,7 +56,7 @@ describe('Generic verification code through management API', () => {
it('should fail to create a verification code on server side when the email and phone are not provided', async () => {
await expectRejects(requestVerificationCode({ username: 'any_string' }), {
code: 'guard.invalid_input',
statusCode: 400,
status: 400,
});
await expect(readConnectorMessage('Email')).rejects.toThrow();
@ -68,20 +68,17 @@ describe('Generic verification code through management API', () => {
await clearConnectorsByTypes([ConnectorType.Email]);
await expectRejects(requestVerificationCode({ email: emailForTestSendCode }), {
code: 'connector.not_found',
statusCode: 501,
status: 501,
});
await expect(
verifyVerificationCode({ email: emailForTestSendCode, verificationCode: 'any_string' })
).rejects.toMatchObject({
response: {
statusCode: 400,
body: JSON.stringify({
message: 'Invalid verification code.',
await expectRejects(
verifyVerificationCode({ email: emailForTestSendCode, verificationCode: 'any_string' }),
{
messageIncludes: 'Invalid verification code.',
code: 'verification_code.code_mismatch',
}),
},
});
status: 400,
}
);
// Restore the email connector
await setEmailConnector();
@ -92,20 +89,17 @@ describe('Generic verification code through management API', () => {
await clearConnectorsByTypes([ConnectorType.Sms]);
await expectRejects(requestVerificationCode({ phone: phoneForTestSendCode }), {
code: 'connector.not_found',
statusCode: 501,
status: 501,
});
await expect(
verifyVerificationCode({ phone: phoneForTestSendCode, verificationCode: 'any_string' })
).rejects.toMatchObject({
response: {
statusCode: 400,
body: JSON.stringify({
message: 'Invalid verification code.',
await expectRejects(
verifyVerificationCode({ phone: phoneForTestSendCode, verificationCode: 'any_string' }),
{
messageIncludes: 'Invalid verification code.',
code: 'verification_code.code_mismatch',
}),
},
});
status: 400,
}
);
// Restore the SMS connector
await setSmsConnector();
@ -136,7 +130,7 @@ describe('Generic verification code through management API', () => {
await readConnectorMessage('Sms');
await expectRejects(verifyVerificationCode({ phone: mockPhone, verificationCode: '666' }), {
code: 'verification_code.code_mismatch',
statusCode: 400,
status: 400,
});
});
@ -148,7 +142,7 @@ describe('Generic verification code through management API', () => {
expect(phoneToGetCode).toEqual(phone);
await expectRejects(verifyVerificationCode({ phone: phoneToVerify, verificationCode: code }), {
code: 'verification_code.not_found',
statusCode: 400,
status: 400,
});
});
@ -160,7 +154,7 @@ describe('Generic verification code through management API', () => {
expect(emailToGetCode).toEqual(address);
await expectRejects(verifyVerificationCode({ email: emailToVerify, verificationCode: code }), {
code: 'verification_code.not_found',
statusCode: 400,
status: 400,
});
});
});

View file

@ -1,5 +1,5 @@
import { type SignInExperience, type Translation, type SsoConnectorMetadata } from '@logto/schemas';
import { HTTPError } from 'got';
import { HTTPError } from 'ky';
import api, { adminTenantApi, authedAdminApi } from '#src/api/api.js';
import { updateSignInExperience } from '#src/api/index.js';
@ -15,7 +15,7 @@ describe('.well-known api', () => {
it('should not found API route in non-admin tenant', async () => {
const response = await api.get('.well-known/endpoints/123').catch((error: unknown) => error);
expect(response instanceof HTTPError && response.response.statusCode === 404).toBe(true);
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
});
it('get /.well-known/sign-in-exp for console', async () => {

View file

@ -1,7 +1,5 @@
/* Test the sign-in with different password policies. */
import crypto from 'node:crypto';
import { ConnectorType, SignInIdentifier } from '@logto/schemas';
import { updateSignInExperience } from '#src/api/sign-in-experience.js';
@ -9,8 +7,7 @@ import { demoAppUrl } from '#src/constants.js';
import { clearConnectorsByTypes, setEmailConnector } from '#src/helpers/connector.js';
import ExpectExperience from '#src/ui-helpers/expect-experience.js';
import { setupUsernameAndEmailExperience } from '#src/ui-helpers/index.js';
const randomString = () => crypto.randomBytes(8).toString('hex');
import { randomString } from '#src/utils.js';
describe('password policy', () => {
const username = 'test_' + randomString();

View file

@ -122,3 +122,5 @@ export const dcls = <C extends string>(className: C) => `div${cls(className)}` a
* ```
*/
export const generateTestName = () => `test_${generateStandardId(4)}`;
export const randomString = () => crypto.randomBytes(8).toString('hex');

View file

@ -2883,8 +2883,8 @@ importers:
specifier: ^4.2.0
version: 4.2.0
ky:
specifier: ^1.0.0
version: 1.0.0
specifier: ^1.2.3
version: 1.2.3
libphonenumber-js:
specifier: ^1.10.51
version: 1.10.51
@ -3535,8 +3535,8 @@ importers:
specifier: ^3.7.5
version: 3.7.5
ky:
specifier: ^1.0.0
version: 1.0.0
specifier: ^1.2.3
version: 1.2.3
libphonenumber-js:
specifier: ^1.10.51
version: 1.10.51
@ -3667,9 +3667,6 @@ importers:
expect-puppeteer:
specifier: ^10.0.0
version: 10.0.0
got:
specifier: ^14.0.0
version: 14.0.0
jest:
specifier: ^29.7.0
version: 29.7.0(@types/node@20.10.4)
@ -3682,9 +3679,9 @@ importers:
jose:
specifier: ^5.0.0
version: 5.0.1
node-fetch:
specifier: ^3.3.0
version: 3.3.0
ky:
specifier: ^1.2.3
version: 1.2.3
openapi-schema-validator:
specifier: ^12.1.3
version: 12.1.3
@ -9465,6 +9462,7 @@ packages:
/@sindresorhus/is@6.1.0:
resolution: {integrity: sha512-BuvU07zq3tQ/2SIgBsEuxKYDyDjC0n7Zir52bpHy2xnBbW81+po43aLFPLbeV3HRAheFbGud1qgcqSYfhtHMAg==}
engines: {node: '>=16'}
dev: false
/@sinonjs/commons@2.0.0:
resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==}
@ -9773,6 +9771,7 @@ packages:
engines: {node: '>=14.16'}
dependencies:
defer-to-connect: 2.0.1
dev: false
/@testing-library/dom@9.0.0:
resolution: {integrity: sha512-+/TLgKNFsYUshOY/zXsQOk+PlFQK+eyJ9T13IDVNJEi+M+Un7xlJK+FZKkbGSnf0+7E1G6PlDhkSYQ/GFiruBQ==}
@ -10012,6 +10011,7 @@ packages:
/@types/http-cache-semantics@4.0.4:
resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==}
dev: false
/@types/http-errors@1.8.2:
resolution: {integrity: sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==}
@ -11338,6 +11338,7 @@ packages:
/cacheable-lookup@7.0.0:
resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
engines: {node: '>=14.16'}
dev: false
/cacheable-request@10.2.14:
resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==}
@ -11350,6 +11351,7 @@ packages:
mimic-response: 4.0.0
normalize-url: 8.0.0
responselike: 3.0.0
dev: false
/call-bind@1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
@ -12168,11 +12170,6 @@ packages:
engines: {node: '>=12'}
dev: true
/data-uri-to-buffer@4.0.0:
resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==}
engines: {node: '>= 12'}
dev: true
/data-uri-to-buffer@5.0.1:
resolution: {integrity: sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==}
engines: {node: '>= 14'}
@ -12263,6 +12260,7 @@ packages:
engines: {node: '>=10'}
dependencies:
mimic-response: 3.1.0
dev: false
/dedent@1.5.1:
resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==}
@ -12307,6 +12305,7 @@ packages:
/defer-to-connect@2.0.1:
resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
engines: {node: '>=10'}
dev: false
/define-lazy-prop@2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
@ -13506,14 +13505,6 @@ packages:
pend: 1.2.0
dev: true
/fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.2.1
dev: true
/figures@5.0.0:
resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==}
engines: {node: '>=14'}
@ -13664,6 +13655,7 @@ packages:
/form-data-encoder@4.0.2:
resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==}
engines: {node: '>= 18'}
dev: false
/form-data@3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
@ -13687,13 +13679,6 @@ packages:
engines: {node: '>=0.4.x'}
dev: true
/formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
dependencies:
fetch-blob: 3.2.0
dev: true
/formidable@2.0.1:
resolution: {integrity: sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==}
dependencies:
@ -14082,6 +14067,7 @@ packages:
lowercase-keys: 3.0.0
p-cancelable: 4.0.1
responselike: 3.0.0
dev: false
/graceful-fs@4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
@ -14382,6 +14368,7 @@ packages:
/http-cache-semantics@4.1.1:
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
dev: false
/http-errors@1.4.0:
resolution: {integrity: sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==}
@ -14459,6 +14446,7 @@ packages:
dependencies:
quick-lru: 5.1.1
resolve-alpn: 1.2.1
dev: false
/https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
@ -16225,8 +16213,8 @@ packages:
- supports-color
dev: false
/ky@1.0.0:
resolution: {integrity: sha512-ptUup6btycj2IXLL/L5Bh/QC9ghYC2k/BsnnSf0ccEap8FpCvJ32FL5s5Potb8NkH6HOTdnVGmyI1wkHZ3mfEQ==}
/ky@1.2.3:
resolution: {integrity: sha512-2IM3VssHfG2zYz2FsHRUqIp8chhLc9uxDMcK2THxgFfv8pQhnMfN8L0ul+iW4RdBl5AglF8ooPIflRm3yNH0IA==}
engines: {node: '>=18'}
dev: true
@ -16597,6 +16585,7 @@ packages:
/lowercase-keys@3.0.0:
resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/lowlight@1.20.0:
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
@ -17241,10 +17230,12 @@ packages:
/mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
dev: false
/mimic-response@4.0.0:
resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
@ -17441,11 +17432,6 @@ packages:
resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==}
dev: true
/node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
dev: true
/node-fetch@2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
@ -17470,15 +17456,6 @@ packages:
whatwg-url: 5.0.0
dev: false
/node-fetch@3.3.0:
resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
data-uri-to-buffer: 4.0.0
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
dev: true
/node-forge@1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'}
@ -17600,6 +17577,7 @@ packages:
/normalize-url@8.0.0:
resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==}
engines: {node: '>=14.16'}
dev: false
/npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
@ -17879,6 +17857,7 @@ packages:
/p-cancelable@4.0.1:
resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==}
engines: {node: '>=14.16'}
dev: false
/p-defer@4.0.0:
resolution: {integrity: sha512-Vb3QRvQ0Y5XnF40ZUWW7JfLogicVh/EnA5gBIvKDJoYpeI82+1E3AlB9yOcKFS0AhHrWVnAQO39fbR0G99IVEQ==}
@ -19499,6 +19478,7 @@ packages:
/resolve-alpn@1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
dev: false
/resolve-cwd@3.0.0:
resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
@ -19560,6 +19540,7 @@ packages:
engines: {node: '>=14.16'}
dependencies:
lowercase-keys: 3.0.0
dev: false
/restore-cursor@4.0.0:
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
@ -21591,11 +21572,6 @@ packages:
resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==}
dev: true
/web-streams-polyfill@3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
engines: {node: '>= 8'}
dev: true
/webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false