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-transform-stub": "^2.0.0",
"jest-transformer-svg": "^2.0.0", "jest-transformer-svg": "^2.0.0",
"just-kebab-case": "^4.2.0", "just-kebab-case": "^4.2.0",
"ky": "^1.0.0", "ky": "^1.2.3",
"libphonenumber-js": "^1.10.51", "libphonenumber-js": "^1.10.51",
"lint-staged": "^15.0.0", "lint-staged": "^15.0.0",
"nanoid": "^5.0.1", "nanoid": "^5.0.1",

View file

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

View file

@ -5,13 +5,3 @@ import { authedAdminTenantApi } from './lib/api/api.js';
await authedAdminTenantApi.patch('sign-in-exp', { await authedAdminTenantApi.patch('sign-in-exp', {
json: { signInMode: 'SignInAndRegister' }, 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 dotenv from 'dotenv';
import { setDefaultOptions } from 'expect-puppeteer'; import { setDefaultOptions } from 'expect-puppeteer';
import fetch from 'node-fetch';
import { TextDecoder, TextEncoder } from 'text-encoder'; import { TextDecoder, TextEncoder } from 'text-encoder';
const { jest } = import.meta; const { jest } = import.meta;
@ -8,9 +7,12 @@ const { jest } = import.meta;
dotenv.config(); dotenv.config();
/* eslint-disable @silverhand/fp/no-mutation */ /* eslint-disable @silverhand/fp/no-mutation */
global.fetch = fetch;
global.TextDecoder = TextDecoder; global.TextDecoder = TextDecoder;
global.TextEncoder = TextEncoder; global.TextEncoder = TextEncoder;
global.fail = (message) => {
throw new Error(message);
};
/* eslint-enable @silverhand/fp/no-mutation */ /* eslint-enable @silverhand/fp/no-mutation */
// GitHub Actions default runners need more time for UI tests // GitHub Actions default runners need more time for UI tests

View file

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

View file

@ -1,9 +1,9 @@
import { appendPath } from '@silverhand/essentials'; import { appendPath } from '@silverhand/essentials';
import { got } from 'got'; import ky from 'ky';
import { logtoConsoleUrl, logtoUrl, logtoCloudUrl } from '#src/constants.js'; import { logtoConsoleUrl, logtoUrl, logtoCloudUrl } from '#src/constants.js';
const api = got.extend({ const api = ky.extend({
prefixUrl: appendPath(new URL(logtoUrl), 'api'), 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'), 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'), prefixUrl: appendPath(new URL(logtoCloudUrl), 'api'),
}); });
export const oidcApi = got.extend({ export const oidcApi = ky.extend({
prefixUrl: appendPath(new URL(logtoUrl), 'oidc'), 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. // 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', { return oidcApi.post('token', {
form: { headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: id, client_id: id,
client_secret: secret, client_secret: secret,
grant_type: 'client_credentials', grant_type: 'client_credentials',
}, }),
}); });
}; };

View file

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

View file

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

View file

@ -1,5 +1,23 @@
import { authedAdminApi } from './api.js'; 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< export class ApiFactory<
Schema extends Record<string, unknown>, Schema extends Record<string, unknown>,
PostData extends Record<string, unknown>, PostData extends Record<string, unknown>,
@ -8,19 +26,23 @@ export class ApiFactory<
constructor(public readonly path: string) {} constructor(public readonly path: string) {}
async create(data: PostData): Promise<Schema> { 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[]> { 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> { 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> { 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> { async delete(id: string): Promise<void> {

View file

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

View file

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

View file

@ -37,7 +37,7 @@ export class OrganizationApi extends ApiFactory<
query?: Query query?: Query
): Promise<[rows: UserWithOrganizationRoles[], totalCount: number]> { ): Promise<[rows: UserWithOrganizationRoles[], totalCount: number]> {
const got = await authedAdminApi.get(`${this.path}/${id}/users`, { searchParams: query }); 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> { async deleteUser(id: string, userId: string): Promise<void> {

View file

@ -1,5 +1,5 @@
import type { Resource, CreateResource } from '@logto/schemas'; import type { Resource, CreateResource } from '@logto/schemas';
import type { OptionsOfTextResponseBody } from 'got'; import { type Options } from 'ky';
import { generateResourceIndicator, generateResourceName } from '#src/utils.js'; 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 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>(); authedAdminApi.get(`resources/${resourceId}`, options).json<Resource>();
export const updateResource = async ( export const updateResource = async (

View file

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

View file

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

View file

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

View file

@ -80,7 +80,7 @@ export const createNewSocialUserWithUsernameAndPassword = async (connectorId: st
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(patchInteractionIdentifiers, { username, password }); await client.successSend(patchInteractionIdentifiers, { username, password });
await client.successSend(putInteractionProfile, { connectorId }); 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 { RoleType } from '@logto/schemas';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { assignRolesToUser, getUserRoles, deleteRoleFromUser } from '#src/api/index.js'; import { assignRolesToUser, getUserRoles, deleteRoleFromUser } from '#src/api/index.js';
import { createRole } from '#src/api/role.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 }); const m2mRole = await createRole({ type: RoleType.MachineToMachine });
await expectRejects(assignRolesToUser(user.id, [m2mRole.id]), { await expectRejects(assignRolesToUser(user.id, [m2mRole.id]), {
code: 'user.invalid_role_type', code: 'user.invalid_role_type',
statusCode: 422, status: 422,
}); });
await assignRolesToUser(user.id, [role1.id, role2.id]); await assignRolesToUser(user.id, [role1.id, role2.id]);
@ -65,6 +65,6 @@ describe('admin console user management (roles)', () => {
const role = await createRole({}); const role = await createRole({});
const response = await deleteRoleFromUser(user.id, role.id).catch((error: unknown) => error); 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 type { Role, User } from '@logto/schemas';
import { assignRolesToUser, authedAdminApi, createUser, deleteUser } from '#src/api/index.js'; 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>( const getUsers = async <T>(
init: string[][] | Record<string, string> | URLSearchParams init: string[][] | Record<string, string> | URLSearchParams
): Promise<{ headers: IncomingHttpHeaders; json: T }> => { ): Promise<{ headers: Headers; json: T }> => {
const { headers, body } = await authedAdminApi.get('users', { const response = await authedAdminApi.get('users', {
searchParams: new URLSearchParams(init), 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', () => { 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 () => { it('should return all users if nothing specified', async () => {
const { headers } = await getUsers<User[]>([]); 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', () => { describe('falling back to `like` mode and matches all available fields if only `search` is specified', () => {
it('should search username', async () => { it('should search username', async () => {
const { headers, json } = await getUsers<User[]>([['search', '%search_tom%']]); 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(); expect(json.length === 5 && json.every((user) => user.name === 'Tom Scott')).toBeTruthy();
}); });
it('should search primaryPhone', async () => { it('should search primaryPhone', async () => {
const { headers, json } = await getUsers<User[]>([['search', '%0000%']]); const { headers, json } = await getUsers<User[]>([['search', '%0000%']]);
expect(headers['total-number']).toEqual('10'); expect(headers.get('total-number')).toEqual('10');
expect( expect(
json.length === 10 && json.every((user) => user.username?.startsWith('search_')) json.length === 10 && json.every((user) => user.username?.startsWith('search_'))
).toBeTruthy(); ).toBeTruthy();
@ -92,7 +90,7 @@ describe('admin console user search params', () => {
['isCaseSensitive', 'true'], ['isCaseSensitive', 'true'],
]); ]);
expect(headers['total-number']).toEqual('0'); expect(headers.get('total-number')).toEqual('0');
expect(json.length === 0).toBeTruthy(); expect(json.length === 0).toBeTruthy();
}); });
@ -102,7 +100,7 @@ describe('admin console user search params', () => {
['mode.name', 'exact'], ['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(); expect(json.length === 2 && json.every((user) => user.name === 'Jerry Swift')).toBeTruthy();
}); });
@ -117,7 +115,7 @@ describe('admin console user search params', () => {
['isCaseSensitive', 'true'], ['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(); 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'], ['isCaseSensitive', 'true'],
]); ]);
expect(headers['total-number']).toEqual('5'); expect(headers.get('total-number')).toEqual('5');
expect( expect(
json.length === 5 && json.every((user) => user.username?.startsWith('search_')) json.length === 5 && json.every((user) => user.username?.startsWith('search_'))
).toBeTruthy(); ).toBeTruthy();
@ -144,7 +142,7 @@ describe('admin console user search params', () => {
['mode.primaryEmail', 'exact'], ['mode.primaryEmail', 'exact'],
]); ]);
expect(headers['total-number']).toEqual('3'); expect(headers.get('total-number')).toEqual('3');
expect( expect(
json.length === 3 && json.every((user) => user.name?.startsWith('Jerry Swift Jr')) json.length === 3 && json.every((user) => user.name?.startsWith('Jerry Swift Jr'))
).toBeTruthy(); ).toBeTruthy();
@ -161,7 +159,7 @@ describe('admin console user search params', () => {
['isCaseSensitive', 'true'], ['isCaseSensitive', 'true'],
]); ]);
expect(headers['total-number']).toEqual('3'); expect(headers.get('total-number')).toEqual('3');
expect( expect(
json.length === 3 && json.every((user) => user.username?.startsWith('search_')) json.length === 3 && json.every((user) => user.username?.startsWith('search_'))
).toBeTruthy(); ).toBeTruthy();
@ -174,7 +172,7 @@ describe('admin console user search params', () => {
['search.primaryEmail', 'jerry_swift_jr_2@geek.best'], ['search.primaryEmail', 'jerry_swift_jr_2@geek.best'],
['search.primaryEmail', 'jerry_swift_jr_jr@gmail.com'], ['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', code: 'request.invalid_input',
statusCode: 400, status: 400,
messageIncludes: 'cannot be empty', messageIncludes: 'cannot be empty',
} }
); );
@ -200,7 +198,7 @@ describe('admin console user search params', () => {
]), ]),
{ {
code: 'request.invalid_input', code: 'request.invalid_input',
statusCode: 400, status: 400,
messageIncludes: 'case-insensitive', messageIncludes: 'case-insensitive',
} }
); );
@ -215,13 +213,13 @@ describe('admin console user search params', () => {
]), ]),
{ {
code: 'request.invalid_input', code: 'request.invalid_input',
statusCode: 400, status: 400,
messageIncludes: 'is not valid', messageIncludes: 'is not valid',
} }
), ),
expectRejects(getUsers<User[]>([['search.email', '%gmail%']]), { expectRejects(getUsers<User[]>([['search.email', '%gmail%']]), {
code: 'request.invalid_input', code: 'request.invalid_input',
statusCode: 400, status: 400,
messageIncludes: 'is not valid', messageIncludes: 'is not valid',
}), }),
expectRejects( expectRejects(
@ -231,7 +229,7 @@ describe('admin console user search params', () => {
]), ]),
{ {
code: 'request.invalid_input', code: 'request.invalid_input',
statusCode: 400, status: 400,
messageIncludes: 'is not valid', messageIncludes: 'is not valid',
} }
), ),
@ -283,7 +281,7 @@ describe('admin console user search params - excludeRoleId', () => {
['excludeRoleId', roles[0]!.id], ['excludeRoleId', roles[0]!.id],
]); ]);
expect(headers['total-number']).toEqual('2'); expect(headers.get('total-number')).toEqual('2');
expect(json).toHaveLength(2); expect(json).toHaveLength(2);
expect(json).toContainEqual(expect.objectContaining({ id: users[1]!.id })); expect(json).toContainEqual(expect.objectContaining({ id: users[1]!.id }));
expect(json).toContainEqual(expect.objectContaining({ id: users[2]!.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], ['excludeRoleId', roles[1]!.id],
]); ]);
expect(headers['total-number']).toEqual('1'); expect(headers.get('total-number')).toEqual('1');
expect(json).toHaveLength(1); expect(json).toHaveLength(1);
expect(json).toContainEqual(expect.objectContaining({ id: users[2]!.id })); 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], ['excludeOrganizationId', organizationApi.organizations[0]!.id],
]); ]);
expect(headers['total-number']).toEqual('1'); expect(headers.get('total-number')).toEqual('1');
expect(json).toHaveLength(1); expect(json).toHaveLength(1);
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[2]!.id })); 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], ['excludeOrganizationId', organizationApi.organizations[1]!.id],
]); ]);
expect(headers['total-number']).toEqual('1'); expect(headers.get('total-number')).toEqual('1');
expect(json).toHaveLength(1); expect(json).toHaveLength(1);
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[0]!.id })); 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], ['excludeOrganizationId', organizationApi.organizations[2]!.id],
]); ]);
expect(headers['total-number']).toEqual('2'); expect(headers.get('total-number')).toEqual('2');
expect(json).toHaveLength(2); expect(json).toHaveLength(2);
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[0]!.id })); expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[0]!.id }));
expect(json).toContainEqual(expect.objectContaining({ id: userApi.users[1]!.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 { UsersPasswordEncryptionMethod, ConnectorType } from '@logto/schemas';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { import {
mockSocialConnectorConfig, mockSocialConnectorConfig,
@ -25,9 +23,13 @@ import {
import { clearConnectorsByTypes } from '#src/helpers/connector.js'; import { clearConnectorsByTypes } from '#src/helpers/connector.js';
import { createUserByAdmin, expectRejects } from '#src/helpers/index.js'; import { createUserByAdmin, expectRejects } from '#src/helpers/index.js';
import { createNewSocialUserWithUsernameAndPassword } from '#src/helpers/interactions.js'; import { createNewSocialUserWithUsernameAndPassword } from '#src/helpers/interactions.js';
import { generateUsername, generateEmail, generatePhone, generatePassword } from '#src/utils.js'; import {
generateUsername,
const randomString = () => crypto.randomBytes(8).toString('hex'); generateEmail,
generatePhone,
generatePassword,
randomString,
} from '#src/utils.js';
describe('admin console user management', () => { describe('admin console user management', () => {
beforeAll(async () => { beforeAll(async () => {
@ -62,8 +64,8 @@ describe('admin console user management', () => {
profile: { gender: 'neutral' }, profile: { gender: 'neutral' },
}); });
const { customData, profile } = await getUser(user.id); const { customData, profile } = await getUser(user.id);
expect(customData).toStrictEqual({ foo: 'bar' }); expect({ ...customData }).toStrictEqual({ foo: 'bar' });
expect(profile).toStrictEqual({ gender: 'neutral' }); expect({ ...profile }).toStrictEqual({ gender: 'neutral' });
}); });
it('should fail when create user with conflict identifiers', async () => { 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 createUserByAdmin({ username, password, primaryEmail, primaryPhone });
await expectRejects(createUserByAdmin({ username, password }), { await expectRejects(createUserByAdmin({ username, password }), {
code: 'user.username_already_in_use', code: 'user.username_already_in_use',
statusCode: 422, status: 422,
}); });
await expectRejects(createUserByAdmin({ primaryEmail }), { await expectRejects(createUserByAdmin({ primaryEmail }), {
code: 'user.email_already_in_use', code: 'user.email_already_in_use',
statusCode: 422, status: 422,
}); });
await expectRejects(createUserByAdmin({ primaryPhone }), { await expectRejects(createUserByAdmin({ primaryPhone }), {
code: 'user.phone_already_in_use', code: 'user.phone_already_in_use',
statusCode: 422, status: 422,
}); });
}); });
it('should fail when get user by invalid id', async () => { it('should fail when get user by invalid id', async () => {
await expectRejects(getUser('invalid-user-id'), { await expectRejects(getUser('invalid-user-id'), {
code: 'entity.not_found', code: 'entity.not_found',
statusCode: 404, status: 404,
}); });
}); });
@ -131,21 +133,21 @@ describe('admin console user management', () => {
}; };
const updatedProfile = await updateUserProfile(user.id, profile); const updatedProfile = await updateUserProfile(user.id, profile);
expect(updatedProfile).toStrictEqual(profile); expect(updatedProfile).toMatchObject(profile);
const patchProfile = { const patchProfile = {
familyName: 'another name', familyName: 'another name',
website: 'https://logto.io/', website: 'https://logto.io/',
}; };
const updatedProfile2 = await updateUserProfile(user.id, patchProfile); 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 () => { it('should respond 422 when no update data provided', async () => {
const user = await createUserByAdmin(); const user = await createUserByAdmin();
await expectRejects(updateUser(user.id, {}), { await expectRejects(updateUser(user.id, {}), {
code: 'entity.invalid_input', code: 'entity.invalid_input',
statusCode: 422, status: 422,
}); });
}); });
@ -160,17 +162,17 @@ describe('admin console user management', () => {
await expectRejects(updateUser(anotherUser.id, { username }), { await expectRejects(updateUser(anotherUser.id, { username }), {
code: 'user.username_already_in_use', code: 'user.username_already_in_use',
statusCode: 422, status: 422,
}); });
await expectRejects(updateUser(anotherUser.id, { primaryEmail }), { await expectRejects(updateUser(anotherUser.id, { primaryEmail }), {
code: 'user.email_already_in_use', code: 'user.email_already_in_use',
statusCode: 422, status: 422,
}); });
await expectRejects(updateUser(anotherUser.id, { primaryPhone }), { await expectRejects(updateUser(anotherUser.id, { primaryPhone }), {
code: 'user.phone_already_in_use', code: 'user.phone_already_in_use',
statusCode: 422, status: 422,
}); });
}); });
@ -183,7 +185,7 @@ describe('admin console user management', () => {
await deleteUser(user.id); await deleteUser(user.id);
const response = await getUser(user.id).catch((error: unknown) => error); 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 () => { 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 () => { it('should return 204 if password is correct', async () => {
const user = await createUserByAdmin({ password: 'new_password' }); 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); await deleteUser(user.id);
}); });
@ -305,7 +307,7 @@ describe('admin console user management', () => {
const user = await createUserByAdmin({ password: 'new_password' }); const user = await createUserByAdmin({ password: 'new_password' });
await expectRejects(verifyUserPassword(user.id, 'wrong_password'), { await expectRejects(verifyUserPassword(user.id, 'wrong_password'), {
code: 'session.invalid_credentials', code: 'session.invalid_credentials',
statusCode: 422, status: 422,
}); });
await deleteUser(user.id); await deleteUser(user.id);
}); });
@ -314,7 +316,7 @@ describe('admin console user management', () => {
const user = await createUserByAdmin(); const user = await createUserByAdmin();
await expectRejects(verifyUserPassword(user.id, ''), { await expectRejects(verifyUserPassword(user.id, ''), {
code: 'guard.invalid_input', code: 'guard.invalid_input',
statusCode: 400, status: 400,
}); });
}); });
}); });

View file

@ -51,7 +51,7 @@ describe('application sign in experience', () => {
setApplicationSignInExperience('non-existent-application', applicationSignInExperiences), setApplicationSignInExperience('non-existent-application', applicationSignInExperiences),
{ {
code: 'entity.not_exists_with_id', 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', code: 'application.third_party_application_only',
statusCode: 422, status: 422,
} }
); );
}); });
@ -112,7 +112,7 @@ describe('application sign in experience', () => {
await expectRejects(getApplicationSignInExperience(application.id), { await expectRejects(getApplicationSignInExperience(application.id), {
code: 'entity.not_exists_with_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', 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', 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', 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', 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', 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', 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', 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 () => { it('should return 404 when trying to get consent scopes from non-existing application', async () => {
await expectRejects(getUserConsentScopes('non-existing-application'), { await expectRejects(getUserConsentScopes('non-existing-application'), {
code: 'entity.not_exists_with_id', 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', 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', code: 'entity.not_found',
statusCode: 404, status: 404,
} }
); );
@ -210,7 +210,7 @@ describe('assign user consent scopes to application', () => {
), ),
{ {
code: 'entity.not_found', code: 'entity.not_found',
statusCode: 404, status: 404,
} }
); );
@ -222,7 +222,7 @@ describe('assign user consent scopes to application', () => {
), ),
{ {
code: 'entity.not_found', code: 'entity.not_found',
statusCode: 404, status: 404,
} }
); );
}); });

View file

@ -1,6 +1,6 @@
import { ApplicationType, RoleType } from '@logto/schemas'; import { ApplicationType, RoleType } from '@logto/schemas';
import { generateStandardId } from '@logto/shared'; import { generateStandardId } from '@logto/shared';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { import {
createApplication, createApplication,
@ -27,7 +27,7 @@ describe('admin console application management (roles)', () => {
const application = await createApplication(generateStandardId(), applicationType); const application = await createApplication(generateStandardId(), applicationType);
const response = await getApplicationRoles(application.id).catch((error: unknown) => error); 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 () => { 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 assignRolesToApplication(application.id, [role.id]);
await expectRejects(assignRolesToApplication(application.id, [role.id]), { await expectRejects(assignRolesToApplication(application.id, [role.id]), {
code: 'application.role_exists', 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]), { await expectRejects(assignRolesToApplication(application.id, [role.id]), {
code: 'application.invalid_type', 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( const response = await deleteRoleFromApplication(application.id, role.id).catch(
(error: unknown) => error (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. // 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 { ApplicationType } from '@logto/schemas';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { import {
createApplication, createApplication,
@ -31,7 +31,7 @@ describe('admin console application', () => {
createApplication('test-create-app', ApplicationType.Native, { createApplication('test-create-app', ApplicationType.Native, {
isThirdParty: true, 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', code: 'application.protected_application_subdomain_exists',
statusCode: 422, status: 422,
} }
); );
await deleteApplication(application.id); 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 () => { it('should throw error when creating a protected application with invalid type', async () => {
await expectRejects(createApplication('test-create-app', ApplicationType.Protected), { await expectRejects(createApplication('test-create-app', ApplicationType.Protected), {
code: 'application.protected_app_metadata_is_required', 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.description).toBe(newApplicationDescription);
expect(updatedApplication.oidcClientMetadata.redirectUris).toEqual(newRedirectUris); expect(updatedApplication.oidcClientMetadata.redirectUris).toEqual(newRedirectUris);
expect(updatedApplication.customClientMetadata).toStrictEqual({ expect({ ...updatedApplication.customClientMetadata }).toStrictEqual({
rotateRefreshToken: true, rotateRefreshToken: true,
refreshTokenTtlInDays: 10, refreshTokenTtlInDays: 10,
}); });
@ -256,6 +256,6 @@ describe('admin console application', () => {
await deleteApplication(application.id); await deleteApplication(application.id);
const response = await getApplication(application.id).catch((error: unknown) => error); 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'; import { logtoConsoleUrl } from '#src/constants.js';
describe('social connector form post callback', () => { describe('social connector form post callback', () => {
const request = got.extend({ const request = ky.extend({
prefixUrl: new URL(logtoConsoleUrl), prefixUrl: new URL(logtoConsoleUrl),
}); });
it('should redirect to the same path with query string', async () => { it('should redirect to the same path with query string', async () => {
const response = await request.post('callback/some_connector_id', { const response = await request.post('callback/some_connector_id', {
json: { some: 'data' }, json: { some: 'data' },
followRedirect: false, redirect: 'manual',
throwHttpErrors: false,
}); });
expect(response.statusCode).toBe(303); expect(response.status).toBe(303);
expect(response.headers.location).toBe('/callback/some_connector_id?some=data'); 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 { import {
mockEmailConnectorConfig, mockEmailConnectorConfig,
@ -160,7 +160,7 @@ test('create connector with non-exist connectorId', async () => {
await cleanUpConnectorTable(); await cleanUpConnectorTable();
await expectRejects(postConnector({ connectorId: 'non-exist-id' }), { await expectRejects(postConnector({ connectorId: 'non-exist-id' }), {
code: 'connector.not_found_with_connector_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' } }), postConnector({ connectorId: mockSocialConnectorId, metadata: { target: 'target' } }),
{ {
code: 'connector.cannot_overwrite_metadata_for_non_standard_connector', 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 postConnector({ connectorId: mockSocialConnectorId });
await expectRejects(postConnector({ connectorId: mockSocialConnectorId }), { await expectRejects(postConnector({ connectorId: mockSocialConnectorId }), {
code: 'connector.multiple_instances_not_supported', 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 }); const { id } = await postConnector({ connectorId: mockSocialConnectorId });
await expectRejects(updateConnectorConfig(id, {}, { target: 'target' }), { await expectRejects(updateConnectorConfig(id, {}, { target: 'target' }), {
code: 'connector.cannot_overwrite_metadata_for_non_standard_connector', 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 { import {
listCustomPhrases, listCustomPhrases,

View file

@ -19,15 +19,15 @@ describe('admin console dashboard', () => {
it('non authorized request should return 401', async () => { it('non authorized request should return 401', async () => {
await expectRejects(api.get('dashboard/users/total'), { await expectRejects(api.get('dashboard/users/total'), {
code: 'auth.authorization_header_missing', code: 'auth.authorization_header_missing',
statusCode: 401, status: 401,
}); });
await expectRejects(api.get('dashboard/users/new'), { await expectRejects(api.get('dashboard/users/new'), {
code: 'auth.authorization_header_missing', code: 'auth.authorization_header_missing',
statusCode: 401, status: 401,
}); });
await expectRejects(api.get('dashboard/users/active'), { await expectRejects(api.get('dashboard/users/active'), {
code: 'auth.authorization_header_missing', 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 { createDomain, deleteDomain, getDomain, getDomains } from '#src/api/domain.js';
import { generateDomain } from '#src/utils.js'; import { generateDomain } from '#src/utils.js';
@ -27,7 +27,7 @@ describe('domains', () => {
await createDomain(); await createDomain();
const response = await createDomain().catch((error: unknown) => error); 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 () => { it('should get domain detail successfully', async () => {
@ -40,7 +40,7 @@ describe('domains', () => {
it('should return 404 if domain does not exist', async () => { it('should return 404 if domain does not exist', async () => {
const response = await getDomain('non_existent_domain').catch((error: unknown) => error); 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 () => { it('should delete domain successfully', async () => {
@ -49,6 +49,6 @@ describe('domains', () => {
await deleteDomain(domain.id); await deleteDomain(domain.id);
const response = await getDomain(domain.id).catch((error: unknown) => error); 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', () => { describe('health check', () => {
it('should have a health state', async () => { 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] } }) .patch(`hooks/${created.id}`, { json: { events: [HookEvent.PostSignIn] } })
.json<Hook>() .json<Hook>()
).toMatchObject({ ...created, events: [HookEvent.PostSignIn] }); ).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}`), { await expectRejects(authedAdminApi.get(`hooks/${created.id}`), {
code: 'entity.not_exists_with_id', code: 'entity.not_exists_with_id',
statusCode: 404, status: 404,
}); });
}); });
@ -48,10 +48,10 @@ describe('hooks', () => {
...created, ...created,
event: HookEvent.PostSignIn, 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}`), { await expectRejects(authedAdminApi.get(`hooks/${created.id}`), {
code: 'entity.not_exists_with_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'); const response = await authedAdminApi.get('hooks?page=1&page_size=20');
expect(response.statusCode).toBe(200); expect(response.status).toBe(200);
expect(response.headers).toHaveProperty('total-number'); expect(response.headers.get('total-number')).toEqual(expect.any(String));
// Clean up // Clean up
await authedAdminApi.delete(`hooks/${created.id}`); await authedAdminApi.delete(`hooks/${created.id}`);
@ -78,7 +78,7 @@ describe('hooks', () => {
}; };
await expectRejects(authedAdminApi.post('hooks', { json: payload }), { await expectRejects(authedAdminApi.post('hooks', { json: payload }), {
code: 'guard.invalid_input', code: 'guard.invalid_input',
statusCode: 400, status: 400,
}); });
}); });
@ -91,7 +91,7 @@ describe('hooks', () => {
}; };
await expectRejects(authedAdminApi.post('hooks', { json: payload }), { await expectRejects(authedAdminApi.post('hooks', { json: payload }), {
code: 'hook.missing_events', code: 'hook.missing_events',
statusCode: 400, status: 400,
}); });
}); });
@ -102,14 +102,14 @@ describe('hooks', () => {
await expectRejects(authedAdminApi.patch('hooks/invalid_id', { json: payload }), { await expectRejects(authedAdminApi.patch('hooks/invalid_id', { json: payload }), {
code: 'entity.not_exists', code: 'entity.not_exists',
statusCode: 404, status: 404,
}); });
}); });
it('should throw error if regenerate a hook signing key with a invalid hook id', async () => { 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'), { await expectRejects(authedAdminApi.patch('hooks/invalid_id/signing-key'), {
code: 'entity.not_exists', 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`, { const response = await authedAdminApi.post(`hooks/${created.id}/test`, {
json: { events: [HookEvent.PostSignIn], config: { url: responseSuccessEndpoint } }, json: { events: [HookEvent.PostSignIn], config: { url: responseSuccessEndpoint } },
}); });
expect(response.statusCode).toBe(204); expect(response.status).toBe(204);
// Clean Up // Clean Up
await authedAdminApi.delete(`hooks/${created.id}`); await authedAdminApi.delete(`hooks/${created.id}`);
@ -51,7 +51,7 @@ describe('hook testing', () => {
}), }),
{ {
code: 'entity.not_exists_with_id', code: 'entity.not_exists_with_id',
statusCode: 404, status: 404,
} }
); );
}); });
@ -65,7 +65,7 @@ describe('hook testing', () => {
}), }),
{ {
code: 'hook.send_test_payload_failed', code: 'hook.send_test_payload_failed',
statusCode: 422, status: 422,
} }
); );
@ -82,7 +82,7 @@ describe('hook testing', () => {
}), }),
{ {
code: 'hook.endpoint_responded_with_error', 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', code: 'session.not_found',
statusCode: 400, status: 400,
} }
); );
}); });
@ -38,7 +38,7 @@ describe('Interaction details guard checking', () => {
it('DELETE /interaction', async () => { it('DELETE /interaction', async () => {
await expectRejects(client.send(deleteInteraction), { await expectRejects(client.send(deleteInteraction), {
code: 'session.not_found', code: 'session.not_found',
statusCode: 400, status: 400,
}); });
}); });
@ -49,7 +49,7 @@ describe('Interaction details guard checking', () => {
}), }),
{ {
code: 'session.not_found', code: 'session.not_found',
statusCode: 400, status: 400,
} }
); );
}); });
@ -62,7 +62,7 @@ describe('Interaction details guard checking', () => {
}), }),
{ {
code: 'session.not_found', code: 'session.not_found',
statusCode: 400, status: 400,
} }
); );
}); });
@ -75,7 +75,7 @@ describe('Interaction details guard checking', () => {
}), }),
{ {
code: 'session.not_found', code: 'session.not_found',
statusCode: 400, status: 400,
} }
); );
}); });
@ -88,7 +88,7 @@ describe('Interaction details guard checking', () => {
}), }),
{ {
code: 'session.not_found', code: 'session.not_found',
statusCode: 400, status: 400,
} }
); );
}); });
@ -96,14 +96,14 @@ describe('Interaction details guard checking', () => {
it('DELETE /interaction/profile', async () => { it('DELETE /interaction/profile', async () => {
await expectRejects(client.send(deleteInteractionProfile), { await expectRejects(client.send(deleteInteractionProfile), {
code: 'session.not_found', code: 'session.not_found',
statusCode: 400, status: 400,
}); });
}); });
it('POST /interaction/submit', async () => { it('POST /interaction/submit', async () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'session.not_found', code: 'session.not_found',
statusCode: 400, status: 400,
}); });
}); });
@ -116,7 +116,7 @@ describe('Interaction details guard checking', () => {
}), }),
{ {
code: 'session.not_found', code: 'session.not_found',
statusCode: 400, status: 400,
} }
); );
}); });
@ -128,7 +128,7 @@ describe('Interaction details guard checking', () => {
}), }),
{ {
code: 'session.not_found', 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', 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', 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', 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', code: 'session.verification_session_not_found',
statusCode: 404, status: 404,
} }
); );
}); });
@ -78,7 +78,7 @@ describe('Interaction details results checking', () => {
const client = await initClient(); const client = await initClient();
await expectRejects(client.send(deleteInteractionProfile), { await expectRejects(client.send(deleteInteractionProfile), {
code: 'session.verification_session_not_found', code: 'session.verification_session_not_found',
statusCode: 404, status: 404,
}); });
}); });
@ -86,7 +86,7 @@ describe('Interaction details results checking', () => {
const client = await initClient(); const client = await initClient();
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'session.verification_session_not_found', 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', 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', code: 'session.verification_session_not_found',
statusCode: 404, status: 404,
} }
); );
}); });

View file

@ -31,7 +31,7 @@ describe('PATCH /interaction/identifiers', () => {
}), }),
{ {
code: 'user.suspended', 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 { putInteraction, sendVerificationCode } from '#src/api/interaction.js';
import { initClient } from '#src/helpers/client.js'; import { initClient } from '#src/helpers/client.js';
import { clearConnectorsByTypes } from '#src/helpers/connector.js';
import { expectRejects } from '#src/helpers/index.js'; import { expectRejects } from '#src/helpers/index.js';
import { generateEmail, generatePhone } from '#src/utils.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 * Note: These test cases are designed to cover exceptional scenarios of API calls that
* cannot be covered within the auth flow. * cannot be covered within the auth flow.
@ -23,7 +28,7 @@ describe('POST /interaction/verification/verification-code', () => {
}), }),
{ {
code: 'connector.not_found', code: 'connector.not_found',
statusCode: 501, status: 501,
} }
); );
}); });
@ -41,7 +46,7 @@ describe('POST /interaction/verification/verification-code', () => {
}), }),
{ {
code: 'connector.not_found', code: 'connector.not_found',
statusCode: 501, status: 501,
} }
); );
}); });

View file

@ -27,7 +27,7 @@ describe('PUT /interaction/event', () => {
}), }),
{ {
code: 'auth.forbidden', code: 'auth.forbidden',
statusCode: 403, status: 403,
} }
); );
@ -41,7 +41,7 @@ describe('PUT /interaction/event', () => {
}), }),
{ {
code: 'auth.forbidden', code: 'auth.forbidden',
statusCode: 403, status: 403,
} }
); );
@ -65,7 +65,7 @@ describe('PUT /interaction/event', () => {
}), }),
{ {
code: 'session.interaction_not_found', code: 'session.interaction_not_found',
statusCode: 404, status: 404,
} }
); );
@ -75,7 +75,7 @@ describe('PUT /interaction/event', () => {
}), }),
{ {
code: 'session.interaction_not_found', code: 'session.interaction_not_found',
statusCode: 404, status: 404,
} }
); );
}); });
@ -96,7 +96,7 @@ describe('PUT /interaction/event', () => {
}), }),
{ {
code: 'session.interaction_not_found', code: 'session.interaction_not_found',
statusCode: 404, status: 404,
} }
); );
@ -111,7 +111,7 @@ describe('PUT /interaction/event', () => {
}), }),
{ {
code: 'session.interaction_not_found', 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', 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', 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', 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', code: 'user.sign_in_method_not_enabled',
statusCode: 422, status: 422,
} }
); );
@ -140,7 +140,7 @@ describe('PUT /interaction', () => {
}), }),
{ {
code: 'verification_code.not_found', code: 'verification_code.not_found',
statusCode: 400, status: 400,
} }
); );
@ -155,7 +155,7 @@ describe('PUT /interaction', () => {
}), }),
{ {
code: 'verification_code.not_found', code: 'verification_code.not_found',
statusCode: 400, status: 400,
} }
); );
@ -177,7 +177,7 @@ describe('PUT /interaction', () => {
}), }),
{ {
code: 'session.invalid_connector_id', code: 'session.invalid_connector_id',
statusCode: 422, status: 422,
} }
); );
}); });
@ -194,7 +194,7 @@ describe('PUT /interaction', () => {
}), }),
{ {
code: 'session.connector_session_not_found', code: 'session.connector_session_not_found',
statusCode: 400, status: 400,
} }
); );
}); });

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -40,7 +40,7 @@ describe('Register with identifiers sad path', () => {
}), }),
{ {
code: 'auth.forbidden', code: 'auth.forbidden',
statusCode: 403, status: 403,
} }
); );
@ -67,7 +67,7 @@ describe('Register with identifiers sad path', () => {
}), }),
{ {
code: 'user.sign_in_method_not_enabled', 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', 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', 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', 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(), { await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist', code: 'user.user_not_exist',
statusCode: 404, status: 404,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -169,7 +169,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist', code: 'user.user_not_exist',
statusCode: 404, status: 404,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -214,7 +214,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile', code: 'user.missing_profile',
statusCode: 422, status: 422,
}); });
// Fulfill user profile // Fulfill user profile
@ -276,7 +276,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile', code: 'user.missing_profile',
statusCode: 422, status: 422,
}); });
// Fulfill user profile with existing password // Fulfill user profile with existing password
@ -287,7 +287,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.password_exists_in_profile', code: 'user.password_exists_in_profile',
statusCode: 400, status: 400,
}); });
await client.successSend(putInteractionProfile, { await client.successSend(putInteractionProfile, {
@ -333,7 +333,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile', code: 'user.missing_profile',
statusCode: 422, status: 422,
}); });
// Fulfill user profile with existing password // Fulfill user profile with existing password
@ -344,7 +344,7 @@ describe('Sign-in flow using verification-code identifiers', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.username_already_in_use', code: 'user.username_already_in_use',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionProfile, { await client.successSend(putInteractionProfile, {

View file

@ -43,7 +43,7 @@ describe('Sign-in flow sad path using verification-code identifiers', () => {
}), }),
{ {
code: 'auth.forbidden', 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', 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', 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', 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', 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', 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', 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(), { await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist', 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(), { await expectRejects(client.submitInteraction(), {
code: 'user.user_not_exist', 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(), { await expectRejects(client.submitInteraction(), {
code: 'user.suspended', 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', 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(), { await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile', code: 'user.missing_profile',
statusCode: 422, status: 422,
}); });
await client.successSend(sendVerificationCode, { await client.successSend(sendVerificationCode, {
@ -223,7 +223,7 @@ describe('Sign-in flow using password identifiers', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile', code: 'user.missing_profile',
statusCode: 422, status: 422,
}); });
await client.successSend(sendVerificationCode, { await client.successSend(sendVerificationCode, {

View file

@ -35,7 +35,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}), }),
{ {
code: 'auth.forbidden', code: 'auth.forbidden',
statusCode: 403, status: 403,
} }
); );
@ -51,7 +51,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}), }),
{ {
code: 'auth.forbidden', code: 'auth.forbidden',
statusCode: 403, status: 403,
} }
); );
@ -67,7 +67,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}), }),
{ {
code: 'auth.forbidden', 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', 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', 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', 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', 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', 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', code: 'session.invalid_credentials',
statusCode: 422, status: 422,
} }
); );
}); });
@ -199,7 +199,7 @@ describe('Sign-in flow sad path using password identifiers', () => {
}), }),
{ {
code: 'user.suspended', 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', code: 'session.sso_enabled',
statusCode: 422, status: 422,
} }
); );
await deleteUser(user.id); 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 { createSsoConnector, deleteSsoConnectorById } from '#src/api/sso-connector.js';
import { logtoUrl } from '#src/constants.js'; import { logtoUrl } from '#src/constants.js';
import { initClient } from '#src/helpers/client.js'; import { initClient } from '#src/helpers/client.js';
import { randomString } from '#src/utils.js';
describe('Single Sign On Happy Path', () => { describe('Single Sign On Happy Path', () => {
const connectorIdMap = new Map<string, SsoConnectorMetadata>(); const connectorIdMap = new Map<string, SsoConnectorMetadata>();
const state = 'foo_state'; const state = 'foo_state';
const redirectUri = 'http://foo.dev/callback'; const redirectUri = 'http://foo.dev/callback';
const domain = `foo${randomString()}.com`;
beforeAll(async () => { beforeAll(async () => {
const { id, connectorName } = await createSsoConnector({ const { id, connectorName } = await createSsoConnector({
providerName: SsoProviderName.OIDC, providerName: SsoProviderName.OIDC,
connectorName: 'test-oidc', connectorName: `test-oidc-${randomString()}`,
domains: ['foo.com'], domains: [domain],
config: { config: {
clientId: 'foo', clientId: 'foo',
clientSecret: 'bar', clientSecret: 'bar',
@ -60,7 +62,7 @@ describe('Single Sign On Happy Path', () => {
const client = await initClient(); const client = await initClient();
const response = await client.send(getSsoConnectorsByEmail, { const response = await client.send(getSsoConnectorsByEmail, {
email: 'bar@foo.com', email: 'bar@' + domain,
}); });
expect(response.length).toBeGreaterThan(0); expect(response.length).toBeGreaterThan(0);
@ -88,7 +90,7 @@ describe('Single Sign On Happy Path', () => {
}); });
const response = await client.send(getSsoConnectorsByEmail, { const response = await client.send(getSsoConnectorsByEmail, {
email: 'bar@foo.com', email: 'bar@' + domain,
}); });
expect(response.length).toBe(0); 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 { createSsoConnector, deleteSsoConnectorById } from '#src/api/sso-connector.js';
import { initClient } from '#src/helpers/client.js'; import { initClient } from '#src/helpers/client.js';
import { expectRejects } from '#src/helpers/index.js'; import { expectRejects } from '#src/helpers/index.js';
import { randomString } from '#src/utils.js';
describe('Single Sign On Sad Path', () => { describe('Single Sign On Sad Path', () => {
const state = 'foo_state'; const state = 'foo_state';
@ -34,7 +35,7 @@ describe('Single Sign On Sad Path', () => {
it('should throw if connector config is invalid', async () => { it('should throw if connector config is invalid', async () => {
const { id } = await createSsoConnector({ const { id } = await createSsoConnector({
providerName: SsoProviderName.OIDC, providerName: SsoProviderName.OIDC,
connectorName: 'test-oidc', connectorName: `test-oidc-${randomString()}`,
}); });
const client = await initClient(); const client = await initClient();
@ -82,7 +83,7 @@ describe('Single Sign On Sad Path', () => {
postSamlAssertion({ connectorId, RelayState: 'foo', SAMLResponse: samlAssertion }), postSamlAssertion({ connectorId, RelayState: 'foo', SAMLResponse: samlAssertion }),
{ {
code: 'session.not_found', code: 'session.not_found',
statusCode: 400, status: 400,
} }
); );
}); });
@ -103,7 +104,7 @@ describe('Single Sign On Sad Path', () => {
postSamlAssertion({ connectorId, RelayState, SAMLResponse: samlAssertion }), postSamlAssertion({ connectorId, RelayState, SAMLResponse: samlAssertion }),
{ {
code: 'connector.authorization_failed', 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]); await clearConnectorsByTypes([ConnectorType.Social, ConnectorType.Email, ConnectorType.Sms]);
}); });
describe.only('register and sign-in', () => { describe('register and sign-in', () => {
const socialUserId = generateUserId(); const socialUserId = generateUserId();
it('register with social', async () => { it('register with social', async () => {
@ -68,7 +68,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -124,7 +124,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -137,7 +137,7 @@ describe('social sign-in', () => {
const { primaryEmail, identities } = await getUser(uid); const { primaryEmail, identities } = await getUser(uid);
expect(primaryEmail).toBe(socialEmail); expect(primaryEmail).toBe(socialEmail);
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
expect(identities[mockSocialConnectorTarget]).toStrictEqual({ expect(identities[mockSocialConnectorTarget]).toMatchObject({
details: { details: {
email: expect.any(String), email: expect.any(String),
id: expect.any(String), id: expect.any(String),
@ -176,7 +176,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -213,7 +213,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -255,7 +255,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(patchInteractionIdentifiers, { await client.successSend(patchInteractionIdentifiers, {
@ -319,7 +319,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -327,7 +327,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.missing_profile', code: 'user.missing_profile',
statusCode: 422, status: 422,
}); });
await client.successSend(patchInteractionProfile, { username: generateUsername() }); await client.successSend(patchInteractionProfile, { username: generateUsername() });
@ -363,7 +363,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -400,7 +400,7 @@ describe('social sign-in', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });

View file

@ -73,7 +73,7 @@ describe('Social identifier interaction sad path', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -101,7 +101,7 @@ describe('Social identifier interaction sad path', () => {
}), }),
{ {
code: 'session.insufficient_info', code: 'session.insufficient_info',
statusCode: 400, status: 400,
} }
); );
}); });
@ -121,7 +121,7 @@ describe('Social identifier interaction sad path', () => {
}), }),
{ {
code: 'guard.invalid_input', code: 'guard.invalid_input',
statusCode: 400, status: 400,
} }
); );
}); });
@ -141,7 +141,7 @@ describe('Social identifier interaction sad path', () => {
}), }),
{ {
code: 'connector.unexpected_type', code: 'connector.unexpected_type',
statusCode: 400, status: 400,
} }
); );
@ -154,7 +154,7 @@ describe('Social identifier interaction sad path', () => {
}), }),
{ {
code: 'connector.unexpected_type', code: 'connector.unexpected_type',
statusCode: 400, status: 400,
} }
); );
}); });
@ -173,7 +173,7 @@ describe('Social identifier interaction sad path', () => {
}), }),
{ {
code: 'entity.not_found', code: 'entity.not_found',
statusCode: 404, status: 404,
} }
); );
}); });
@ -195,7 +195,7 @@ describe('Social identifier interaction sad path', () => {
}), }),
{ {
code: 'session.invalid_connector_id', code: 'session.invalid_connector_id',
statusCode: 422, status: 422,
} }
); );
}); });
@ -217,7 +217,7 @@ describe('Social identifier interaction sad path', () => {
}), }),
{ {
code: 'session.invalid_connector_id', 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', 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', 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', 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', code: 'session.connector_session_not_found',
statusCode: 400, status: 400,
} }
); );
}); });
@ -318,7 +318,7 @@ describe('Social identifier interaction sad path', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.suspended', code: 'user.suspended',
statusCode: 401, status: 401,
}); });
// Reset // Reset
@ -350,7 +350,7 @@ describe('Social identifier interaction sad path', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'user.identity_not_exist', code: 'user.identity_not_exist',
statusCode: 422, status: 422,
}); });
await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register });
@ -361,7 +361,7 @@ describe('Social identifier interaction sad path', () => {
await expectRejects(client.submitInteraction(), { await expectRejects(client.submitInteraction(), {
code: 'session.connector_session_not_found', 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 () => { it('should throw on getting non-exist log detail', async () => {
await expectRejects(getLog('non-exist-log-id'), { await expectRejects(getLog('non-exist-log-id'), {
code: 'entity.not_exists_with_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); expect(privateKeys).toHaveLength(1);
await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.PrivateKeys, privateKeys[0]!.id), { await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.PrivateKeys, privateKeys[0]!.id), {
code: 'oidc.key_required', code: 'oidc.key_required',
statusCode: 422, status: 422,
}); });
const cookieKeys = await getOidcKeys(LogtoOidcConfigKeyType.CookieKeys); const cookieKeys = await getOidcKeys(LogtoOidcConfigKeyType.CookieKeys);
expect(cookieKeys).toHaveLength(1); expect(cookieKeys).toHaveLength(1);
await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.CookieKeys, cookieKeys[0]!.id), { await expectRejects(deleteOidcKey(LogtoOidcConfigKeyType.CookieKeys, cookieKeys[0]!.id), {
code: 'oidc.key_required', code: 'oidc.key_required',
statusCode: 422, status: 422,
}); });
}); });
@ -141,11 +141,11 @@ describe('admin console sign-in experience', () => {
await expectRejects(getJwtCustomizer('access-token'), { await expectRejects(getJwtCustomizer('access-token'), {
code: 'entity.not_exists', code: 'entity.not_exists',
statusCode: 404, status: 404,
}); });
await expectRejects(deleteJwtCustomizer('access-token'), { await expectRejects(deleteJwtCustomizer('access-token'), {
code: 'entity.not_found', code: 'entity.not_found',
statusCode: 404, status: 404,
}); });
const accessToken = await upsertJwtCustomizer('access-token', accessTokenJwtCustomizerPayload); const accessToken = await upsertJwtCustomizer('access-token', accessTokenJwtCustomizerPayload);
expect(accessToken).toMatchObject(accessTokenJwtCustomizerPayload); expect(accessToken).toMatchObject(accessTokenJwtCustomizerPayload);
@ -164,7 +164,7 @@ describe('admin console sign-in experience', () => {
await expect(deleteJwtCustomizer('access-token')).resolves.not.toThrow(); await expect(deleteJwtCustomizer('access-token')).resolves.not.toThrow();
await expectRejects(getJwtCustomizer('access-token'), { await expectRejects(getJwtCustomizer('access-token'), {
code: 'entity.not_exists', code: 'entity.not_exists',
statusCode: 404, status: 404,
}); });
}); });
@ -177,11 +177,11 @@ describe('admin console sign-in experience', () => {
await expectRejects(getJwtCustomizer('client-credentials'), { await expectRejects(getJwtCustomizer('client-credentials'), {
code: 'entity.not_exists', code: 'entity.not_exists',
statusCode: 404, status: 404,
}); });
await expectRejects(deleteJwtCustomizer('client-credentials'), { await expectRejects(deleteJwtCustomizer('client-credentials'), {
code: 'entity.not_found', code: 'entity.not_found',
statusCode: 404, status: 404,
}); });
const clientCredentials = await upsertJwtCustomizer( const clientCredentials = await upsertJwtCustomizer(
'client-credentials', 'client-credentials',
@ -203,7 +203,7 @@ describe('admin console sign-in experience', () => {
await expect(deleteJwtCustomizer('client-credentials')).resolves.not.toThrow(); await expect(deleteJwtCustomizer('client-credentials')).resolves.not.toThrow();
await expectRejects(getJwtCustomizer('client-credentials'), { await expectRejects(getJwtCustomizer('client-credentials'), {
code: 'entity.not_exists', 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 { logtoConsoleUrl, logtoUrl } from '#src/constants.js';
import { import {
@ -11,35 +11,35 @@ import { expectRejects } from '#src/helpers/index.js';
describe('me', () => { describe('me', () => {
it('should only be available in admin tenant', async () => { 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', code: 'auth.authorization_header_missing',
statusCode: 401, status: 401,
}); });
// Redirect to UI // Redirect to UI
const response = await got.get(new URL('/me/custom-data', logtoUrl)); const response = await ky.get(new URL('/me/custom-data', logtoUrl));
expect(response.statusCode).toBe(200); expect(response.status).toBe(200);
expect(response.headers['content-type']?.startsWith('text/html;')).toBeTruthy(); expect(response.headers.get('content-type')?.startsWith('text/html;')).toBeTruthy();
}); });
it('should only recognize the access token with correct resource and scope', async () => { it('should only recognize the access token with correct resource and scope', async () => {
const { id, client } = await createUserWithAllRolesAndSignInToClient(); const { id, client } = await createUserWithAllRolesAndSignInToClient();
await expectRejects( await expectRejects(
got.get(logtoConsoleUrl + '/me/custom-data', { ky.get(logtoConsoleUrl + '/me/custom-data', {
headers: { authorization: `Bearer ${await client.getAccessToken(resourceDefault)}` }, headers: { authorization: `Bearer ${await client.getAccessToken(resourceDefault)}` },
}), }),
{ {
code: 'auth.unauthorized', code: 'auth.unauthorized',
statusCode: 401, status: 401,
} }
); );
await expect( await expect(
got.get(logtoConsoleUrl + '/me/custom-data', { ky.get(logtoConsoleUrl + '/me/custom-data', {
headers: { authorization: `Bearer ${await client.getAccessToken(resourceMe)}` }, headers: { authorization: `Bearer ${await client.getAccessToken(resourceMe)}` },
}) })
).resolves.toHaveProperty('statusCode', 200); ).resolves.toHaveProperty('status', 200);
await deleteUser(id); await deleteUser(id);
}); });
@ -48,14 +48,14 @@ describe('me', () => {
const { id, client } = await createUserWithAllRolesAndSignInToClient(); const { id, client } = await createUserWithAllRolesAndSignInToClient();
const headers = { authorization: `Bearer ${await client.getAccessToken(resourceMe)}` }; const headers = { authorization: `Bearer ${await client.getAccessToken(resourceMe)}` };
const data = await got const data = await ky
.get(logtoConsoleUrl + '/me/custom-data', { headers }) .get(logtoConsoleUrl + '/me/custom-data', { headers })
.json<Record<string, unknown>>(); .json<Record<string, unknown>>();
const newData = await got const newData = await ky
.patch(logtoConsoleUrl + '/me/custom-data', { headers, json: { foo: 'bar' } }) .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); await deleteUser(id);
}); });

View file

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

View file

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

View file

@ -2,7 +2,7 @@ import assert from 'node:assert';
import { ConnectorType } from '@logto/connector-kit'; import { ConnectorType } from '@logto/connector-kit';
import { generateStandardId } from '@logto/shared'; import { generateStandardId } from '@logto/shared';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { createUser } from '#src/api/admin-user.js'; import { createUser } from '#src/api/admin-user.js';
import { clearConnectorsByTypes, setEmailConnector } from '#src/helpers/connector.js'; import { clearConnectorsByTypes, setEmailConnector } from '#src/helpers/connector.js';
@ -11,12 +11,10 @@ import { OrganizationApiTest, OrganizationInvitationApiTest } from '#src/helpers
const randomId = () => generateStandardId(4); 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); assert(error instanceof HTTPError);
const { statusCode, body: raw } = error.response; expect(error.response.status).toBe(statusCode);
const body: unknown = JSON.parse(String(raw)); expect(await error.response.json()).toMatchObject({ code });
expect(statusCode).toBe(status);
expect(body).toMatchObject({ code });
}; };
describe('organization invitation creation', () => { describe('organization invitation creation', () => {
@ -105,7 +103,7 @@ describe('organization invitation creation', () => {
}) })
.catch((error: unknown) => error); .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 () => { 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); .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 () => { 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); .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 () => { 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); .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 () => { 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); .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 () => { 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); .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 () => { 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 { OrganizationInvitationStatus } from '@logto/schemas';
import { generateStandardId } from '@logto/shared'; import { generateStandardId } from '@logto/shared';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { OrganizationApiTest, OrganizationInvitationApiTest } from '#src/helpers/organization.js'; import { OrganizationApiTest, OrganizationInvitationApiTest } from '#src/helpers/organization.js';
import { UserApiTest } from '#src/helpers/user.js'; import { UserApiTest } from '#src/helpers/user.js';
const randomId = () => generateStandardId(4); 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); assert(error instanceof HTTPError);
const { statusCode, body: raw } = error.response; expect(error.response.status).toBe(statusCode);
const body: unknown = JSON.parse(String(raw)); expect(await error.response.json()).toMatchObject({ code });
expect(statusCode).toBe(status);
expect(body).toMatchObject({ code });
}; };
describe('organization invitation status update', () => { describe('organization invitation status update', () => {
@ -50,7 +48,7 @@ describe('organization invitation status update', () => {
const error = await invitationApi const error = await invitationApi
.updateStatus(invitation.id, OrganizationInvitationStatus.Accepted) .updateStatus(invitation.id, OrganizationInvitationStatus.Accepted)
.catch((error: unknown) => error); .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 () => { 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) .updateStatus(invitation.id, OrganizationInvitationStatus.Accepted, user.id)
.catch((error: unknown) => error); .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 () => { 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') .updateStatus(invitation.id, OrganizationInvitationStatus.Accepted, 'invalid')
.catch((error: unknown) => error); .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 () => { 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) .updateStatus(invitation.id, OrganizationInvitationStatus.Accepted)
.catch((error: unknown) => error); .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 { generateStandardId } from '@logto/shared';
import { isKeyInObject, pick } from '@silverhand/essentials'; import { isKeyInObject, pick } from '@silverhand/essentials';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { OrganizationRoleApiTest, OrganizationScopeApiTest } from '#src/helpers/organization.js'; import { OrganizationRoleApiTest, OrganizationScopeApiTest } from '#src/helpers/organization.js';
@ -25,9 +25,8 @@ describe('organization role APIs', () => {
assert(response instanceof HTTPError); assert(response instanceof HTTPError);
const { statusCode, body: raw } = response.response; const body: unknown = await response.response.json();
const body: unknown = JSON.parse(String(raw)); expect(response.response.status).toBe(422);
expect(statusCode).toBe(422);
expect(isKeyInObject(body, 'code') && body.code).toBe('entity.unique_integrity_violation'); 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 () => { 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); 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 () => { it('should be able to update organization role', async () => {
@ -126,8 +125,8 @@ describe('organization role APIs', () => {
.catch((error: unknown) => error); .catch((error: unknown) => error);
assert(response instanceof HTTPError); assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422); expect(response.response.status).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject( expect(await response.response.json()).toMatchObject(
expect.objectContaining({ expect.objectContaining({
code: 'entity.unique_integrity_violation', code: 'entity.unique_integrity_violation',
}) })
@ -138,12 +137,12 @@ describe('organization role APIs', () => {
const createdRole = await roleApi.create({ name: 'test' + randomId() }); const createdRole = await roleApi.create({ name: 'test' + randomId() });
await roleApi.delete(createdRole.id); await roleApi.delete(createdRole.id);
const response = await roleApi.get(createdRole.id).catch((error: unknown) => error); 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 () => { 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); 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); .catch((error: unknown) => error);
assert(response instanceof HTTPError); assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422); expect(response.response.status).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject( expect(await response.response.json()).toMatchObject(
expect.objectContaining({ expect.objectContaining({
code: 'entity.relation_foreign_key_not_found', 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); const response = await roleApi.deleteScope(role.id, '0').catch((error: unknown) => error);
assert(response instanceof HTTPError); 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 { generateStandardId } from '@logto/shared';
import { isKeyInObject } from '@silverhand/essentials'; import { isKeyInObject } from '@silverhand/essentials';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { OrganizationScopeApiTest } from '#src/helpers/organization.js'; import { OrganizationScopeApiTest } from '#src/helpers/organization.js';
@ -22,9 +22,8 @@ describe('organization scope APIs', () => {
assert(response instanceof HTTPError); assert(response instanceof HTTPError);
const { statusCode, body: raw } = response.response; const body: unknown = await response.response.json();
const body: unknown = JSON.parse(String(raw)); expect(response.response.status).toBe(422);
expect(statusCode).toBe(422);
expect(isKeyInObject(body, 'code') && body.code).toBe('entity.unique_integrity_violation'); 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 () => { 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); 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 () => { it('should be able to update organization scope', async () => {
@ -99,8 +98,8 @@ describe('organization scope APIs', () => {
.catch((error: unknown) => error); .catch((error: unknown) => error);
assert(response instanceof HTTPError); assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422); expect(response.response.status).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject( expect(await response.response.json()).toMatchObject(
expect.objectContaining({ expect.objectContaining({
code: 'entity.unique_integrity_violation', code: 'entity.unique_integrity_violation',
}) })
@ -111,11 +110,11 @@ describe('organization scope APIs', () => {
const createdScope = await scopeApi.create({ name: 'test' + randomId() }); const createdScope = await scopeApi.create({ name: 'test' + randomId() });
await scopeApi.delete(createdScope.id); await scopeApi.delete(createdScope.id);
const response = await scopeApi.get(createdScope.id).catch((error: unknown) => error); 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 () => { 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); 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 assert from 'node:assert';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { OrganizationApiTest } from '#src/helpers/organization.js'; import { OrganizationApiTest } from '#src/helpers/organization.js';
import { UserApiTest } from '#src/helpers/user.js'; import { UserApiTest } from '#src/helpers/user.js';
@ -95,14 +95,14 @@ describe('organization user APIs', () => {
const response = await organizationApi const response = await organizationApi
.addUsers(organization.id, []) .addUsers(organization.id, [])
.catch((error: unknown) => error); .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 () => { 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); const response = await organizationApi.addUsers('0', ['0']).catch((error: unknown) => error);
assert(response instanceof HTTPError); assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422); expect(response.response.status).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject( expect(await response.response.json()).toMatchObject(
expect.objectContaining({ code: 'entity.relation_foreign_key_not_found' }) 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 () => { 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); const response = await organizationApi.deleteUser('0', '0').catch((error: unknown) => error);
assert(response instanceof HTTPError); 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); .catch((error: unknown) => error);
assert(response instanceof HTTPError); assert(response instanceof HTTPError);
expect(response.response.statusCode).toBe(422); expect(response.response.status).toBe(422);
expect(JSON.parse(String(response.response.body))).toMatchObject( expect(await response.response.json()).toMatchObject(
expect.objectContaining({ code: 'organization.require_membership' }) expect.objectContaining({ code: 'organization.require_membership' })
); );
@ -244,7 +244,7 @@ describe('organization user APIs', () => {
const response = await organizationApi const response = await organizationApi
.getUserRoles(organization.id, user.id) .getUserRoles(organization.id, user.id)
.catch((error: unknown) => error); .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 { OrganizationApiTest } from '#src/helpers/organization.js';
import { UserApiTest } from '#src/helpers/user.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 () => { it('should fail when try to get an organization that does not exist', async () => {
const response = await organizationApi.get('0').catch((error: unknown) => error); 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 () => { it('should be able to update organization', async () => {
@ -117,11 +117,11 @@ describe('organization APIs', () => {
const response = await organizationApi const response = await organizationApi
.get(createdOrganization.id) .get(createdOrganization.id)
.catch((error: unknown) => error); .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 () => { it('should fail when try to delete an organization that does not exist', async () => {
const response = await organizationApi.delete('0').catch((error: unknown) => error); 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 assert from 'node:assert';
import { defaultManagementApi } from '@logto/schemas'; import { defaultManagementApi } from '@logto/schemas';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { createResource } from '#src/api/index.js'; import { createResource } from '#src/api/index.js';
import { createScope, deleteScope, getScopes, updateScope } from '#src/api/scope.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( const response = await createScope(resource.id, createdScope.name).catch(
(error: unknown) => error (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 () => { it('should return 404 when create scope with invalid resource id', async () => {
const response = await createScope('invalid_resource_id', 'invalid_scope_name').catch( const response = await createScope('invalid_resource_id', 'invalid_scope_name').catch(
(error: unknown) => error (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 () => { it('should return 400 if scope name is empty', async () => {
const resource = await createResource(); const resource = await createResource();
const response = await createScope(resource.id, '').catch((error: unknown) => error); 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 () => { it('should return 400 if scope name has empty space', async () => {
const resource = await createResource(); const resource = await createResource();
const response = await createScope(resource.id, 'scope id').catch((error: unknown) => error); 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 () => { it('should update scope successfully', async () => {
@ -82,7 +82,7 @@ describe('scopes', () => {
const response = await updateScope(resource.id, createdScope2.id, { const response = await updateScope(resource.id, createdScope2.id, {
name: createdScope.name, name: createdScope.name,
}).catch((error: unknown) => error); }).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 () => { it('should return 400 if update scope name that has empty space', async () => {
@ -93,7 +93,7 @@ describe('scopes', () => {
}).catch((error: unknown) => error); }).catch((error: unknown) => error);
assert(response instanceof HTTPError); 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 () => { it('should return 404 when update scope with invalid resource id', async () => {
@ -101,7 +101,7 @@ describe('scopes', () => {
name: 'scope', name: 'scope',
}).catch((error: unknown) => error); }).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 () => { it('should return 404 when update scope with invalid scope id', async () => {
@ -111,7 +111,7 @@ describe('scopes', () => {
name: 'scope', name: 'scope',
}).catch((error: unknown) => error); }).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 () => { it('should delete scope successfully', async () => {

View file

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

View file

@ -1,6 +1,6 @@
import { ApplicationType, RoleType } from '@logto/schemas'; import { ApplicationType, RoleType } from '@logto/schemas';
import { generateStandardId } from '@logto/shared'; import { generateStandardId } from '@logto/shared';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { assignRolesToApplication, createApplication } from '#src/api/index.js'; import { assignRolesToApplication, createApplication } from '#src/api/index.js';
import { import {
@ -27,7 +27,7 @@ describe('roles applications', () => {
it('should return 404 if role not found', async () => { it('should return 404 if role not found', async () => {
const response = await getRoleApplications('not-found').catch((error: unknown) => error); 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 () => { 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 () => { it('should fail when try to assign empty applications', async () => {
const role = await createRole({ type: RoleType.MachineToMachine }); const role = await createRole({ type: RoleType.MachineToMachine });
const response = await assignApplicationsToRole([], role.id).catch((error: unknown) => error); 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 () => { it('should fail with invalid application input', async () => {
const role = await createRole({ type: RoleType.MachineToMachine }); const role = await createRole({ type: RoleType.MachineToMachine });
const response = await assignApplicationsToRole([''], role.id).catch((error: unknown) => error); 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 () => { it('should fail if role not found', async () => {
@ -66,7 +66,7 @@ describe('roles applications', () => {
const response = await assignApplicationsToRole([m2mApp.id], 'not-found').catch( const response = await assignApplicationsToRole([m2mApp.id], 'not-found').catch(
(error: unknown) => error (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 () => { it('should fail if application not found', async () => {
@ -74,7 +74,7 @@ describe('roles applications', () => {
const response = await assignApplicationsToRole(['not-found'], role.id).catch( const response = await assignApplicationsToRole(['not-found'], role.id).catch(
(error: unknown) => error (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 () => { it('should remove application from role successfully', async () => {
@ -95,7 +95,7 @@ describe('roles applications', () => {
const response = await deleteApplicationFromRole(m2mApp.id, 'not-found').catch( const response = await deleteApplicationFromRole(m2mApp.id, 'not-found').catch(
(error: unknown) => error (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 () => { 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( const response = await deleteApplicationFromRole('not-found', role.id).catch(
(error: unknown) => error (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. // 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 { defaultManagementApi } from '@logto/schemas';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { createResource } from '#src/api/index.js'; import { createResource } from '#src/api/index.js';
import { import {
@ -24,7 +24,7 @@ describe('roles scopes', () => {
it('should return 404 if role not found', async () => { it('should return 404 if role not found', async () => {
const response = await getRoleScopes('not-found').catch((error: unknown) => error); 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 () => { 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 () => { it('should fail when try to assign empty scopes', async () => {
const role = await createRole({}); const role = await createRole({});
const response = await assignScopesToRole([], role.id).catch((error: unknown) => error); 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 () => { it('should fail with invalid scope input', async () => {
const role = await createRole({}); const role = await createRole({});
const response = await assignScopesToRole([''], role.id).catch((error: unknown) => error); 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 () => { it('should fail if role not found', async () => {
@ -61,7 +61,7 @@ describe('roles scopes', () => {
const response = await assignScopesToRole([scope.id], 'not-found').catch( const response = await assignScopesToRole([scope.id], 'not-found').catch(
(error: unknown) => error (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 () => { it('should fail if scope not found', async () => {
@ -69,7 +69,7 @@ describe('roles scopes', () => {
const response = await assignScopesToRole(['not-found'], role.id).catch( const response = await assignScopesToRole(['not-found'], role.id).catch(
(error: unknown) => error (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 () => { 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( const response = await assignScopesToRole([scope1.id, scope2.id], role.id).catch(
(error: unknown) => error (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 () => { 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], [defaultManagementApi.scopes[0]!.id],
userRole.id userRole.id
).catch((error: unknown) => error); ).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 () => { it('should remove scope from role successfully', async () => {
@ -113,7 +113,7 @@ describe('roles scopes', () => {
const resource = await createResource(); const resource = await createResource();
const scope = await createScope(resource.id); const scope = await createScope(resource.id);
const response = await deleteScopeFromRole(scope.id, role.id).catch((error: unknown) => error); 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 () => { 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( const response = await deleteScopeFromRole(scope.id, 'not-found').catch(
(error: unknown) => error (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 () => { it('should fail when try to assign a scope to an internal role', async () => {
@ -132,6 +132,6 @@ describe('roles scopes', () => {
(error: unknown) => error (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 { defaultManagementApi } from '@logto/schemas';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { createResource } from '#src/api/resource.js'; import { createResource } from '#src/api/resource.js';
import { import {
@ -49,13 +49,13 @@ describe('roles', () => {
const { name } = await createRole({}); const { name } = await createRole({});
const response = await createRole({ name }).catch((error: unknown) => error); 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 () => { it('should fail when try to create an internal role', async () => {
const response = await createRole({ name: '#internal:foo' }).catch((error: unknown) => error); 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 () => { it('should fail when try to create role with management API scope(s)', async () => {
@ -63,7 +63,7 @@ describe('roles', () => {
(error: unknown) => error (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 () => { it('should get role detail successfully', async () => {
@ -77,7 +77,7 @@ describe('roles', () => {
it('should return 404 if role does not exist', async () => { it('should return 404 if role does not exist', async () => {
const response = await getRole('non_existent_role').catch((error: unknown) => error); 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 () => { it('should update role details successfully', async () => {
@ -103,14 +103,14 @@ describe('roles', () => {
const response = await updateRole(role2.id, { const response = await updateRole(role2.id, {
name: role1.name, name: role1.name,
}).catch((error: unknown) => error); }).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 () => { it('should fail when update a non-existent role', async () => {
const response = await updateRole('non_existent_role', { const response = await updateRole('non_existent_role', {
name: 'new_name', name: 'new_name',
}).catch((error: unknown) => error); }).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 () => { it('should fail when try to update an internal role', async () => {
@ -120,7 +120,7 @@ describe('roles', () => {
name: '#internal:foo', name: '#internal:foo',
}).catch((error: unknown) => error); }).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 () => { it('should delete role successfully', async () => {
@ -129,12 +129,12 @@ describe('roles', () => {
await deleteRole(role.id); await deleteRole(role.id);
const response = await getRole(role.id).catch((error: unknown) => error); 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 () => { it('should return 404 if role does not exist', async () => {
const response = await deleteRole('non_existent_role').catch((error: unknown) => error); 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 { RoleType } from '@logto/schemas';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { createUser } from '#src/api/index.js'; import { createUser } from '#src/api/index.js';
import { import {
@ -28,7 +28,7 @@ describe('roles users', () => {
it('should return 404 if role not found', async () => { it('should return 404 if role not found', async () => {
const response = await getRoleUsers('not-found').catch((error: unknown) => error); 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 () => { it('should assign users to role successfully', async () => {
@ -66,7 +66,7 @@ describe('roles users', () => {
const user = await createUser(generateNewUserProfile({})); const user = await createUser(generateNewUserProfile({}));
await expectRejects(assignUsersToRole([user.id], m2mRole.id), { await expectRejects(assignUsersToRole([user.id], m2mRole.id), {
code: 'entity.db_constraint_violated', code: 'entity.db_constraint_violated',
statusCode: 422, status: 422,
}); });
const users = await getRoleUsers(m2mRole.id); const users = await getRoleUsers(m2mRole.id);
@ -76,13 +76,13 @@ describe('roles users', () => {
it('should fail when try to assign empty users', async () => { it('should fail when try to assign empty users', async () => {
const role = await createRole({}); const role = await createRole({});
const response = await assignUsersToRole([], role.id).catch((error: unknown) => error); 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 () => { it('should fail with invalid user input', async () => {
const role = await createRole({}); const role = await createRole({});
const response = await assignUsersToRole([''], role.id).catch((error: unknown) => error); 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 () => { it('should fail if role not found', async () => {
@ -90,7 +90,7 @@ describe('roles users', () => {
const response = await assignUsersToRole([user.id], 'not-found').catch( const response = await assignUsersToRole([user.id], 'not-found').catch(
(error: unknown) => error (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 () => { it('should fail if user not found', async () => {
@ -98,7 +98,7 @@ describe('roles users', () => {
const response = await assignUsersToRole(['not-found'], role.id).catch( const response = await assignUsersToRole(['not-found'], role.id).catch(
(error: unknown) => error (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 () => { it('should remove user from role successfully', async () => {
@ -119,7 +119,7 @@ describe('roles users', () => {
const response = await deleteUserFromRole(user.id, 'not-found').catch( const response = await deleteUserFromRole(user.id, 'not-found').catch(
(error: unknown) => error (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 () => { 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( const response = await deleteUserFromRole('not-found', role.id).catch(
(error: unknown) => error (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), { await expectRejects(updateSignInExperience(newSignInExperience), {
code: 'sign_in_experiences.username_requires_password', code: 'sign_in_experiences.username_requires_password',
statusCode: 400, status: 400,
}); });
}); });
}); });

View file

@ -1,5 +1,5 @@
import { SsoProviderName } from '@logto/schemas'; import { SsoProviderName } from '@logto/schemas';
import { HTTPError } from 'got'; import { HTTPError } from 'ky';
import { import {
providerNames, providerNames,
@ -64,7 +64,7 @@ describe('post sso-connectors', () => {
providerName: 'OIDC', providerName: 'OIDC',
connectorName: 'test connector name', 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); await deleteSsoConnectorById(id);
@ -202,7 +202,7 @@ describe('patch sso-connector by id', () => {
patchSsoConnectorById(id2, { patchSsoConnectorById(id2, {
connectorName: 'test connector name', 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); await deleteSsoConnectorById(id);

View file

@ -9,11 +9,11 @@ const { default: OpenApiSchemaValidator } = Validator;
describe('Swagger check', () => { describe('Swagger check', () => {
it('should provide a valid swagger.json', async () => { it('should provide a valid swagger.json', async () => {
const response = await api.get('swagger.json'); const response = await api.get('swagger.json');
expect(response).toHaveProperty('statusCode', 200); expect(response).toHaveProperty('status', 200);
expect(response.headers['content-type']).toContain('application/json'); expect(response.headers.get('content-type')).toContain('application/json');
// Use multiple validators to be more confident // 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 validator = new OpenApiSchemaValidator({ version: 3 });
const result = validator.validate(object as OpenAPI.Document); 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 () => { it('should create an email verification code on server side', async () => {
const payload: RequestVerificationCodePayload = { email: mockEmail }; const payload: RequestVerificationCodePayload = { email: mockEmail };
const response = await requestVerificationCode(payload); const response = await requestVerificationCode(payload);
expect(response.statusCode).toBe(204); expect(response.status).toBe(204);
const { code, type, address } = await readConnectorMessage('Email'); 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 () => { it('should create an SMS verification code on server side', async () => {
const payload: RequestVerificationCodePayload = { phone: mockPhone }; const payload: RequestVerificationCodePayload = { phone: mockPhone };
const response = await requestVerificationCode(payload); const response = await requestVerificationCode(payload);
expect(response.statusCode).toBe(204); expect(response.status).toBe(204);
const { code, type, phone } = await readConnectorMessage('Sms'); 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 () => { 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' }), { await expectRejects(requestVerificationCode({ username: 'any_string' }), {
code: 'guard.invalid_input', code: 'guard.invalid_input',
statusCode: 400, status: 400,
}); });
await expect(readConnectorMessage('Email')).rejects.toThrow(); await expect(readConnectorMessage('Email')).rejects.toThrow();
@ -68,20 +68,17 @@ describe('Generic verification code through management API', () => {
await clearConnectorsByTypes([ConnectorType.Email]); await clearConnectorsByTypes([ConnectorType.Email]);
await expectRejects(requestVerificationCode({ email: emailForTestSendCode }), { await expectRejects(requestVerificationCode({ email: emailForTestSendCode }), {
code: 'connector.not_found', code: 'connector.not_found',
statusCode: 501, status: 501,
}); });
await expect( await expectRejects(
verifyVerificationCode({ email: emailForTestSendCode, verificationCode: 'any_string' }) verifyVerificationCode({ email: emailForTestSendCode, verificationCode: 'any_string' }),
).rejects.toMatchObject({ {
response: { messageIncludes: 'Invalid verification code.',
statusCode: 400, code: 'verification_code.code_mismatch',
body: JSON.stringify({ status: 400,
message: 'Invalid verification code.', }
code: 'verification_code.code_mismatch', );
}),
},
});
// Restore the email connector // Restore the email connector
await setEmailConnector(); await setEmailConnector();
@ -92,20 +89,17 @@ describe('Generic verification code through management API', () => {
await clearConnectorsByTypes([ConnectorType.Sms]); await clearConnectorsByTypes([ConnectorType.Sms]);
await expectRejects(requestVerificationCode({ phone: phoneForTestSendCode }), { await expectRejects(requestVerificationCode({ phone: phoneForTestSendCode }), {
code: 'connector.not_found', code: 'connector.not_found',
statusCode: 501, status: 501,
}); });
await expect( await expectRejects(
verifyVerificationCode({ phone: phoneForTestSendCode, verificationCode: 'any_string' }) verifyVerificationCode({ phone: phoneForTestSendCode, verificationCode: 'any_string' }),
).rejects.toMatchObject({ {
response: { messageIncludes: 'Invalid verification code.',
statusCode: 400, code: 'verification_code.code_mismatch',
body: JSON.stringify({ status: 400,
message: 'Invalid verification code.', }
code: 'verification_code.code_mismatch', );
}),
},
});
// Restore the SMS connector // Restore the SMS connector
await setSmsConnector(); await setSmsConnector();
@ -136,7 +130,7 @@ describe('Generic verification code through management API', () => {
await readConnectorMessage('Sms'); await readConnectorMessage('Sms');
await expectRejects(verifyVerificationCode({ phone: mockPhone, verificationCode: '666' }), { await expectRejects(verifyVerificationCode({ phone: mockPhone, verificationCode: '666' }), {
code: 'verification_code.code_mismatch', code: 'verification_code.code_mismatch',
statusCode: 400, status: 400,
}); });
}); });
@ -148,7 +142,7 @@ describe('Generic verification code through management API', () => {
expect(phoneToGetCode).toEqual(phone); expect(phoneToGetCode).toEqual(phone);
await expectRejects(verifyVerificationCode({ phone: phoneToVerify, verificationCode: code }), { await expectRejects(verifyVerificationCode({ phone: phoneToVerify, verificationCode: code }), {
code: 'verification_code.not_found', code: 'verification_code.not_found',
statusCode: 400, status: 400,
}); });
}); });
@ -160,7 +154,7 @@ describe('Generic verification code through management API', () => {
expect(emailToGetCode).toEqual(address); expect(emailToGetCode).toEqual(address);
await expectRejects(verifyVerificationCode({ email: emailToVerify, verificationCode: code }), { await expectRejects(verifyVerificationCode({ email: emailToVerify, verificationCode: code }), {
code: 'verification_code.not_found', 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 { 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 api, { adminTenantApi, authedAdminApi } from '#src/api/api.js';
import { updateSignInExperience } from '#src/api/index.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 () => { it('should not found API route in non-admin tenant', async () => {
const response = await api.get('.well-known/endpoints/123').catch((error: unknown) => error); 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 () => { it('get /.well-known/sign-in-exp for console', async () => {

View file

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

View file

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