Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use ky in integration tests #5584

Merged
merged 4 commits into from Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,22 +1,22 @@
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'),
});

export default api;

// TODO: @gao rename

Check warning on line 12 in packages/integration-tests/src/api/api.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/integration-tests/src/api/api.ts#L12

[no-warning-comments] Unexpected 'todo' comment: 'TODO: @gao rename'.
export const authedAdminApi = api.extend({
headers: {
'development-user-id': 'integration-test-admin-user',
},
});

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

Expand All @@ -26,10 +26,10 @@
},
});

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