Skip to content

Commit

Permalink
refactor: use ky in integration tests (#5584)
Browse files Browse the repository at this point in the history
* refactor: use ky in integration tests

* refactor: remove node-fetch

* refactor: fix test cases

* refactor: remove waitFor after each test
  • Loading branch information
gao-sun committed Mar 29, 2024
1 parent eb4c6c4 commit 6d56434
Show file tree
Hide file tree
Showing 85 changed files with 556 additions and 582 deletions.
2 changes: 1 addition & 1 deletion packages/console/package.json
Expand Up @@ -84,7 +84,7 @@
"jest-transform-stub": "^2.0.0",
"jest-transformer-svg": "^2.0.0",
"just-kebab-case": "^4.2.0",
"ky": "^1.0.0",
"ky": "^1.2.3",
"libphonenumber-js": "^1.10.51",
"lint-staged": "^15.0.0",
"nanoid": "^5.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/experience/package.json
Expand Up @@ -67,7 +67,7 @@
"jest-transform-stub": "^2.0.0",
"jest-transformer-svg": "^2.0.0",
"js-base64": "^3.7.5",
"ky": "^1.0.0",
"ky": "^1.2.3",
"libphonenumber-js": "^1.10.51",
"lint-staged": "^15.0.0",
"parcel": "2.9.3",
Expand Down
10 changes: 0 additions & 10 deletions packages/integration-tests/jest.setup.api.js
Expand Up @@ -5,13 +5,3 @@ import { authedAdminTenantApi } from './lib/api/api.js';
await authedAdminTenantApi.patch('sign-in-exp', {
json: { signInMode: 'SignInAndRegister' },
});

const waitFor = async (ms) =>
new Promise((resolve) => {
setTimeout(resolve, ms);
});

global.afterEach(async () => {
// Try to mitigate the issue of "Socket hang up". See https://github.com/nodejs/node/issues/47130
await waitFor(1);
});
6 changes: 4 additions & 2 deletions packages/integration-tests/jest.setup.js
@@ -1,16 +1,18 @@
import dotenv from 'dotenv';
import { setDefaultOptions } from 'expect-puppeteer';
import fetch from 'node-fetch';
import { TextDecoder, TextEncoder } from 'text-encoder';

const { jest } = import.meta;

dotenv.config();

/* eslint-disable @silverhand/fp/no-mutation */
global.fetch = fetch;
global.TextDecoder = TextDecoder;
global.TextEncoder = TextEncoder;
global.fail = (message) => {
throw new Error(message);
};

/* eslint-enable @silverhand/fp/no-mutation */

// GitHub Actions default runners need more time for UI tests
Expand Down
3 changes: 1 addition & 2 deletions packages/integration-tests/package.json
Expand Up @@ -38,12 +38,11 @@
"dotenv": "^16.0.0",
"eslint": "^8.44.0",
"expect-puppeteer": "^10.0.0",
"got": "^14.0.0",
"jest": "^29.7.0",
"jest-matcher-specific-error": "^1.0.0",
"jest-puppeteer": "^10.0.1",
"jose": "^5.0.0",
"node-fetch": "^3.3.0",
"ky": "^1.2.3",
"openapi-schema-validator": "^12.1.3",
"openapi-types": "^12.1.3",
"prettier": "^3.0.0",
Expand Down
10 changes: 5 additions & 5 deletions packages/integration-tests/src/api/api.ts
@@ -1,9 +1,9 @@
import { appendPath } from '@silverhand/essentials';
import { got } from 'got';
import ky from 'ky';

import { logtoConsoleUrl, logtoUrl, logtoCloudUrl } from '#src/constants.js';

const api = got.extend({
const api = ky.extend({
prefixUrl: appendPath(new URL(logtoUrl), 'api'),
});

Expand All @@ -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'),
});

Expand All @@ -26,10 +26,10 @@ export const authedAdminTenantApi = adminTenantApi.extend({
},
});

export const cloudApi = got.extend({
export const cloudApi = ky.extend({
prefixUrl: appendPath(new URL(logtoCloudUrl), 'api'),
});

export const oidcApi = got.extend({
export const oidcApi = ky.extend({
prefixUrl: appendPath(new URL(logtoUrl), 'oidc'),
});
7 changes: 5 additions & 2 deletions packages/integration-tests/src/api/application.ts
Expand Up @@ -99,10 +99,13 @@ export const generateM2mLog = async (applicationId: string) => {

// This is a token request with insufficient parameters and should fail. We make the request to generate a log for the current machine to machine app.
return oidcApi.post('token', {
form: {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: id,
client_secret: secret,
grant_type: 'client_credentials',
},
}),
});
};
14 changes: 5 additions & 9 deletions packages/integration-tests/src/api/connector.ts
Expand Up @@ -31,23 +31,21 @@ export const postConnector = async (
payload: Pick<CreateConnector, 'connectorId' | 'config' | 'metadata' | 'syncProfile'>
) =>
authedAdminApi
.post({
url: `connectors`,
.post('connectors', {
json: payload,
})
.json<Connector>();

export const deleteConnectorById = async (id: string) =>
authedAdminApi.delete({ url: `connectors/${id}` }).json();
authedAdminApi.delete(`connectors/${id}`).json();

export const updateConnectorConfig = async (
id: string,
config: Record<string, unknown>,
metadata?: Record<string, unknown>
) =>
authedAdminApi
.patch({
url: `connectors/${id}`,
.patch(`connectors/${id}`, {
json: { config, metadata },
})
.json<ConnectorResponse>();
Expand All @@ -70,8 +68,7 @@ const sendTestMessage = async (
receiver: string,
config: Record<string, unknown>
) =>
authedAdminApi.post({
url: `connectors/${connectorFactoryId}/test`,
authedAdminApi.post(`connectors/${connectorFactoryId}/test`, {
json: { [receiverType]: receiver, config },
});

Expand All @@ -81,8 +78,7 @@ export const getConnectorAuthorizationUri = async (
redirectUri: string
) =>
authedAdminApi
.post({
url: `connectors/${connectorId}/authorization-uri`,
.post(`connectors/${connectorId}/authorization-uri`, {
json: { state, redirectUri },
})
.json<{ redirectTo: string }>();
4 changes: 1 addition & 3 deletions packages/integration-tests/src/api/custom-phrase.ts
Expand Up @@ -9,9 +9,7 @@ export const getCustomPhrase = async (languageTag: string) =>
authedAdminApi.get(`custom-phrases/${languageTag}`).json<CustomPhrase>();

export const createOrUpdateCustomPhrase = async (languageTag: string, translation: Translation) =>
authedAdminApi
.put({ url: `custom-phrases/${languageTag}`, json: translation })
.json<CustomPhrase>();
authedAdminApi.put(`custom-phrases/${languageTag}`, { json: translation }).json<CustomPhrase>();

export const deleteCustomPhrase = async (languageTag: string) =>
authedAdminApi.delete(`custom-phrases/${languageTag}`).json();
30 changes: 26 additions & 4 deletions packages/integration-tests/src/api/factory.ts
@@ -1,5 +1,23 @@
import { authedAdminApi } from './api.js';

/**
* Transform the data to a new object or array without modifying the original data.
* This is useful since `.json()` returns an object that contains something which makes
* it impossible to use `.toStrictEqual()` directly.
*/
const transform = <T>(data: T): T => {
if (Array.isArray(data)) {
// eslint-disable-next-line no-restricted-syntax
return [...data] as T;
}

if (typeof data === 'object') {
return { ...data };
}

return data;
};

export class ApiFactory<
Schema extends Record<string, unknown>,
PostData extends Record<string, unknown>,
Expand All @@ -8,19 +26,23 @@ export class ApiFactory<
constructor(public readonly path: string) {}

async create(data: PostData): Promise<Schema> {
return authedAdminApi.post(this.path, { json: data }).json<Schema>();
return transform(await authedAdminApi.post(this.path, { json: data }).json<Schema>());
}

async getList(params?: URLSearchParams): Promise<Schema[]> {
return authedAdminApi.get(this.path + '?' + (params?.toString() ?? '')).json<Schema[]>();
return transform(
await authedAdminApi.get(this.path + '?' + (params?.toString() ?? '')).json<Schema[]>()
);
}

async get(id: string): Promise<Schema> {
return authedAdminApi.get(this.path + '/' + id).json<Schema>();
return transform(await authedAdminApi.get(this.path + '/' + id).json<Schema>());
}

async update(id: string, data: PatchData): Promise<Schema> {
return authedAdminApi.patch(this.path + '/' + id, { json: data }).json<Schema>();
return transform(
await authedAdminApi.patch(this.path + '/' + id, { json: data }).json<Schema>()
);
}

async delete(id: string): Promise<void> {
Expand Down
1 change: 0 additions & 1 deletion packages/integration-tests/src/api/interaction-sso.ts
Expand Up @@ -15,7 +15,6 @@ export const getSsoAuthorizationUrl = async (
.post(`interaction/${ssoPath}/${connectorId}/authorization-url`, {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json<{ redirectTo: string }>();
};
Expand Down
31 changes: 9 additions & 22 deletions packages/integration-tests/src/api/interaction.ts
Expand Up @@ -7,7 +7,7 @@ import type {
VerifyMfaPayload,
ConsentInfoResponse,
} from '@logto/schemas';
import type { Got } from 'got';
import { type KyInstance } from 'ky';

import api from './api.js';

Expand All @@ -26,29 +26,24 @@ export const putInteraction = async (cookie: string, payload: InteractionPayload
.put('interaction', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();

export const deleteInteraction = async (cookie: string) =>
api
.delete('interaction', {
headers: { cookie },
followRedirect: false,
})
.json();

export const putInteractionEvent = async (cookie: string, payload: { event: InteractionEvent }) =>
api
.put('interaction/event', { headers: { cookie }, json: payload, followRedirect: false })
.json();
api.put('interaction/event', { headers: { cookie }, json: payload, redirect: 'manual' }).json();

export const patchInteractionIdentifiers = async (cookie: string, payload: IdentifierPayload) =>
api
.patch('interaction/identifiers', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();

Expand All @@ -57,7 +52,6 @@ export const patchInteractionProfile = async (cookie: string, payload: Profile)
.patch('interaction/profile', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();

Expand All @@ -66,7 +60,6 @@ export const putInteractionProfile = async (cookie: string, payload: Profile) =>
.put('interaction/profile', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();

Expand All @@ -75,7 +68,6 @@ export const postInteractionBindMfa = async (cookie: string, payload: BindMfaPay
.post('interaction/bind-mfa', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();

Expand All @@ -84,21 +76,19 @@ export const putInteractionMfa = async (cookie: string, payload: VerifyMfaPayloa
.put('interaction/mfa', {
headers: { cookie },
json: payload,
followRedirect: false,
})
.json();

export const deleteInteractionProfile = async (cookie: string) =>
api
.delete('interaction/profile', {
headers: { cookie },
followRedirect: false,
})
.json();

export const submitInteraction = async (api: Got, cookie: string) =>
export const submitInteraction = async (api: KyInstance, cookie: string) =>
api
.post('interaction/submit', { headers: { cookie }, followRedirect: false })
.post('interaction/submit', { headers: { cookie }, redirect: 'manual' })
.json<RedirectResponse>();

export const sendVerificationCode = async (
Expand All @@ -108,7 +98,6 @@ export const sendVerificationCode = async (
api.post('interaction/verification/verification-code', {
headers: { cookie },
json: payload,
followRedirect: false,
});

export type SocialAuthorizationUriPayload = {
Expand All @@ -124,15 +113,15 @@ export const createSocialAuthorizationUri = async (
api.post('interaction/verification/social-authorization-uri', {
headers: { cookie },
json: payload,
followRedirect: false,
});

export const initTotp = async (cookie: string) =>
api
.post('interaction/verification/totp', {
headers: { cookie },
json: {},
followRedirect: false,
redirect: 'manual',
throwHttpErrors: false,
})
.json<{ secret: string }>();

Expand All @@ -142,24 +131,23 @@ export const skipMfaBinding = async (cookie: string) =>
json: {
mfaSkipped: true,
},
followRedirect: false,
});

export const consent = async (api: Got, cookie: string) =>
export const consent = async (api: KyInstance, cookie: string) =>
api
.post('interaction/consent', {
headers: {
cookie,
},
followRedirect: false,
redirect: 'manual',
throwHttpErrors: false,
})
.json<RedirectResponse>();

export const getConsentInfo = async (cookie: string) =>
api
.get('interaction/consent', {
headers: { cookie },
followRedirect: false,
})
.json<ConsentInfoResponse>();

Expand All @@ -170,5 +158,4 @@ export const createSingleSignOnAuthorizationUri = async (
api.post('interaction/verification/sso-authorization-uri', {
headers: { cookie },
json: payload,
followRedirect: false,
});
2 changes: 1 addition & 1 deletion packages/integration-tests/src/api/organization.ts
Expand Up @@ -37,7 +37,7 @@ export class OrganizationApi extends ApiFactory<
query?: Query
): Promise<[rows: UserWithOrganizationRoles[], totalCount: number]> {
const got = await authedAdminApi.get(`${this.path}/${id}/users`, { searchParams: query });
return [JSON.parse(got.body), Number(got.headers['total-number'] ?? 0)];
return [await got.json(), Number(got.headers.get('total-number') ?? 0)];
}

async deleteUser(id: string, userId: string): Promise<void> {
Expand Down

0 comments on commit 6d56434

Please sign in to comment.