chore(deps): upgrade to msw v2 [EE-6489] (#10911)

pull/10292/head^2
Chaim Lev-Ari 2024-01-04 16:57:21 +07:00 committed by GitHub
parent ecd603db8c
commit 400a80c07d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2602 additions and 1669 deletions

View File

@ -1,7 +1,9 @@
import { http, HttpResponse } from 'msw';
import { renderWithQueryClient, within } from '@/react-tools/test-utils';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { server, rest } from '@/setup-tests/server';
import { server } from '@/setup-tests/server';
import {
createMockResourceGroups,
createMockSubscriptions,
@ -106,30 +108,27 @@ async function renderComponent(
const state = { user };
server.use(
rest.get(
'/api/endpoints/:endpointId/azure/subscriptions',
(req, res, ctx) =>
res(
ctx.json(createMockSubscriptions(subscriptionsCount)),
ctx.status(subscriptionsStatus)
)
http.get('/api/endpoints/:endpointId/azure/subscriptions', () =>
HttpResponse.json(createMockSubscriptions(subscriptionsCount), {
status: subscriptionsStatus,
})
),
rest.get(
http.get(
'/api/endpoints/:endpointId/azure/subscriptions/:subscriptionId/resourcegroups',
(req, res, ctx) => {
if (typeof req.params.subscriptionId !== 'string') {
({ params }) => {
if (typeof params.subscriptionId !== 'string') {
throw new Error("Provided subscriptionId must be of type: 'string'");
}
const { subscriptionId } = req.params;
return res(
ctx.json(
const { subscriptionId } = params;
return HttpResponse.json(
createMockResourceGroups(
req.params.subscriptionId,
subscriptionId,
resourceGroups[subscriptionId] || 0
)
),
ctx.status(resourceGroupsStatus)
{
status: resourceGroupsStatus,
}
);
}
)

View File

@ -1,6 +1,8 @@
import { http, HttpResponse } from 'msw';
import { Tag, TagId } from '@/portainer/tags/types';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { server, rest } from '@/setup-tests/server';
import { server } from '@/setup-tests/server';
import { TagSelector } from './TagSelector';
@ -49,7 +51,7 @@ async function renderComponent(
} = {},
tags: Tag[] = []
) {
server.use(rest.get('/api/tags', (_req, res, ctx) => res(ctx.json(tags))));
server.use(http.get('/api/tags', () => HttpResponse.json(tags)));
const queries = renderWithQueryClient(
<TagSelector value={value} allowCreate={allowCreate} onChange={onChange} />

View File

@ -1,4 +1,6 @@
import { server, rest } from '@/setup-tests/server';
import { http, HttpResponse } from 'msw';
import { server } from '@/setup-tests/server';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { isoDate } from '@/portainer/filters/filters';
@ -7,8 +9,8 @@ import { BackupFailedPanel } from './BackupFailedPanel';
test('when backup failed, should show message', async () => {
const timestamp = 1500;
server.use(
rest.get('/api/backup/s3/status', (req, res, ctx) =>
res(ctx.json({ Failed: true, TimestampUTC: timestamp }))
http.get('/api/backup/s3/status', () =>
HttpResponse.json({ Failed: true, TimestampUTC: timestamp })
)
);
@ -26,8 +28,8 @@ test('when backup failed, should show message', async () => {
test("when user is using less nodes then allowed he shouldn't see message", async () => {
server.use(
rest.get('/api/backup/s3/status', (req, res, ctx) =>
res(ctx.json({ Failed: false }))
http.get('/api/backup/s3/status', () =>
HttpResponse.json({ Failed: false })
)
);
const { findByText } = renderWithQueryClient(<BackupFailedPanel />);

View File

@ -1,3 +1,5 @@
import { http, HttpResponse } from 'msw';
import {
EnvironmentGroup,
EnvironmentGroupId,
@ -8,7 +10,7 @@ import { UserViewModel } from '@/portainer/models/user';
import { Tag } from '@/portainer/tags/types';
import { createMockEnvironment } from '@/react-tools/test-mocks';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { server, rest } from '@/setup-tests/server';
import { server } from '@/setup-tests/server';
import { EnvironmentItem } from './EnvironmentItem';
@ -39,7 +41,7 @@ function renderComponent(
) {
const user = new UserViewModel({ Username: 'test', Role: isAdmin ? 1 : 2 });
server.use(rest.get('/api/tags', (req, res, ctx) => res(ctx.json(tags))));
server.use(http.get('/api/tags', () => HttpResponse.json(tags)));
return renderWithQueryClient(
<UserContext.Provider value={{ user }}>

View File

@ -1,8 +1,10 @@
import { http, HttpResponse } from 'msw';
import { Environment } from '@/react/portainer/environments/types';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { rest, server } from '@/setup-tests/server';
import { server } from '@/setup-tests/server';
import { EnvironmentList } from './EnvironmentList';
@ -37,12 +39,13 @@ async function renderComponent(
const user = new UserViewModel({ Username: 'test', Role: isAdmin ? 1 : 2 });
server.use(
rest.get('/api/endpoints', (req, res, ctx) =>
res(
ctx.set('x-total-available', environments.length.toString()),
ctx.set('x-total-count', environments.length.toString()),
ctx.json(environments)
)
http.get('/api/endpoints', () =>
HttpResponse.json(environments, {
headers: {
'x-total-available': environments.length.toString(),
'x-total-count': environments.length.toString(),
},
})
)
);

View File

@ -1,4 +1,6 @@
import { server, rest } from '@/setup-tests/server';
import { http, HttpResponse } from 'msw';
import { server } from '@/setup-tests/server';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { LicenseType } from '../licenses/types';
@ -9,12 +11,10 @@ test('when user is using more nodes then allowed he should see message', async (
const allowed = 2;
const used = 5;
server.use(
rest.get('/api/licenses/info', (req, res, ctx) =>
res(ctx.json({ nodes: allowed, type: LicenseType.Subscription }))
http.get('/api/licenses/info', () =>
HttpResponse.json({ nodes: allowed, type: LicenseType.Subscription })
),
rest.get('/api/system/nodes', (req, res, ctx) =>
res(ctx.json({ nodes: used }))
)
http.get('/api/system/nodes', () => HttpResponse.json({ nodes: used }))
);
const { findByText } = renderWithQueryClient(<LicenseNodePanel />);
@ -30,12 +30,10 @@ test("when user is using less nodes then allowed he shouldn't see message", asyn
const allowed = 5;
const used = 2;
server.use(
rest.get('/api/licenses/info', (req, res, ctx) =>
res(ctx.json({ nodes: allowed, type: LicenseType.Subscription }))
http.get('/api/licenses/info', () =>
HttpResponse.json({ nodes: allowed, type: LicenseType.Subscription })
),
rest.get('/api/system/nodes', (req, res, ctx) =>
res(ctx.json({ nodes: used }))
)
http.get('/api/system/nodes', () => HttpResponse.json({ nodes: used }))
);
const { findByText } = renderWithQueryClient(<LicenseNodePanel />);

View File

@ -1,4 +1,6 @@
import { server, rest } from '@/setup-tests/server';
import { http, HttpResponse } from 'msw';
import { server } from '@/setup-tests/server';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient, within } from '@/react-tools/test-utils';
@ -305,11 +307,11 @@ async function renderComponent(
const state = { user };
if (teams) {
server.use(rest.get('/api/teams', (req, res, ctx) => res(ctx.json(teams))));
server.use(http.get('/api/teams', () => HttpResponse.json(teams)));
}
if (users) {
server.use(rest.get('/api/users', (req, res, ctx) => res(ctx.json(users))));
server.use(http.get('/api/users', () => HttpResponse.json(users)));
}
const renderResult = renderWithQueryClient(

View File

@ -1,8 +1,9 @@
import _ from 'lodash';
import { http, HttpResponse } from 'msw';
import { createMockTeams, createMockUsers } from '@/react-tools/test-mocks';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { rest, server } from '@/setup-tests/server';
import { server } from '@/setup-tests/server';
import { Role } from '@/portainer/users/types';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
@ -86,7 +87,7 @@ for (let i = 0; i < inheritanceTests.length; i += 1) {
test('when resource is limited to specific users, show number of users', async () => {
const users = createMockUsers(10, Role.Standard);
server.use(rest.get('/api/users', (req, res, ctx) => res(ctx.json(users))));
server.use(http.get('/api/users', () => HttpResponse.json(users)));
const restrictedToUsers = _.sampleSize(users, 3);
@ -114,7 +115,7 @@ test('when resource is limited to specific users, show number of users', async (
test('when resource is limited to specific teams, show comma separated list of their names', async () => {
const teams = createMockTeams(10);
server.use(rest.get('/api/teams', (req, res, ctx) => res(ctx.json(teams))));
server.use(http.get('/api/teams', () => HttpResponse.json(teams)));
const restrictedToTeams = _.sampleSize(teams, 3);

View File

@ -1,6 +1,6 @@
import { Meta } from '@storybook/react';
import { Form, Formik } from 'formik';
import { rest } from 'msw';
import { http, HttpResponse } from 'msw';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { GitCredential } from '@/react/portainer/account/git-credentials/types';
@ -14,28 +14,25 @@ export default {
parameters: {
msw: {
handlers: [
rest.get<Array<GitCredential>, { userId: string }>(
http.get<{ userId: string }, Array<GitCredential>>(
'/api/users/:userId/gitcredentials',
(req, res, ctx) =>
res(
ctx.status(200),
ctx.json<Array<GitCredential>>([
({ params }) =>
HttpResponse.json([
{
id: 1,
name: 'credential-1',
username: 'username-1',
userId: parseInt(req.params.userId, 10),
userId: parseInt(params.userId, 10),
creationDate: 0,
},
{
id: 2,
name: 'credential-2',
username: 'username-2',
userId: parseInt(req.params.userId, 10),
userId: parseInt(params.userId, 10),
creationDate: 0,
},
])
)
),
],
},

View File

@ -1,4 +1,6 @@
import { server, rest } from '@/setup-tests/server';
import { http, HttpResponse } from 'msw';
import { server } from '@/setup-tests/server';
import { getLicenses } from './license.service';
import type { License } from './types';
@ -9,9 +11,7 @@ describe('getLicenses', () => {
const thenFn = jest.fn();
const data: License[] = [];
server.use(
rest.get('/api/licenses', (req, res, ctx) => res(ctx.json(data)))
);
server.use(http.get('/api/licenses', () => HttpResponse.json(data)));
const promise = getLicenses();
@ -29,8 +29,8 @@ describe('getLicenses', () => {
const details = 'details';
server.use(
rest.get('/api/licenses', (req, res, ctx) =>
res(ctx.status(400), ctx.json({ message, details }))
http.get('/api/licenses', () =>
HttpResponse.json({ message, details }, { status: 400 })
)
);

View File

@ -0,0 +1,42 @@
/* eslint-disable import/order */
/* eslint-disable @typescript-eslint/no-var-requires */
/**
* @note The block below contains polyfills for Node.js globals
* required for Jest to function when running JSDOM tests.
* These HAVE to be require's and HAVE to be in this exact
* order, since "undici" depends on the "TextEncoder" global API.
*
* Consider migrating to a more modern test runner if
* you don't want to deal with this.
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const { TextDecoder, TextEncoder } = require('node:util');
Object.defineProperties(globalThis, {
TextDecoder: { value: TextDecoder },
TextEncoder: { value: TextEncoder },
});
const { ReadableStream } = require('node:stream/web');
Object.defineProperties(globalThis, {
ReadableStream: { value: ReadableStream },
});
const { Blob, File } = require('node:buffer');
const { fetch, Headers, FormData, Request, Response } = require('undici');
Object.defineProperties(globalThis, {
fetch: { value: fetch, writable: true },
Blob: { value: Blob },
File: { value: File },
Headers: { value: Headers },
FormData: { value: FormData },
Request: { value: Request },
Response: { value: Response },
});
/* eslint-enable @typescript-eslint/no-var-requires */
/* eslint-enable import/order */

View File

@ -1,4 +1,4 @@
import { DefaultBodyType, PathParams, rest } from 'msw';
import { http, HttpResponse } from 'msw';
import {
Edition,
@ -35,48 +35,44 @@ const licenseInfo: LicenseInfo = {
};
export const handlers = [
rest.get('/api/teams', async (req, res, ctx) =>
res(ctx.json(createMockTeams(10)))
),
http.get('/api/teams', async () => HttpResponse.json(createMockTeams(10))),
rest.post<{ name: string }>('/api/teams', (req, res, ctx) =>
res(ctx.status(204))
http.post<{ name: string }>('/api/teams', () =>
HttpResponse.json(null, { status: 204 })
),
rest.post<{ userId: UserId }>('/api/team_memberships', (req, res, ctx) =>
res(ctx.status(204))
http.post<never, { userId: UserId }>('/api/team_memberships', () =>
HttpResponse.json(null, { status: 204 })
),
...azureHandlers,
...dockerHandlers,
...userHandlers,
rest.get('/api/licenses/info', (req, res, ctx) => res(ctx.json(licenseInfo))),
rest.get('/api/status/nodes', (req, res, ctx) => res(ctx.json({ nodes: 3 }))),
rest.get('/api/backup/s3/status', (req, res, ctx) =>
res(ctx.json({ Failed: false }))
),
rest.get('/api/endpoint_groups', (req, res, ctx) => res(ctx.json([]))),
rest.get('/api/endpoint_groups/:groupId', (req, res, ctx) => {
if (req.params.groupId instanceof Array) {
http.get('/api/licenses/info', () => HttpResponse.json(licenseInfo)),
http.get('/api/status/nodes', () => HttpResponse.json({ nodes: 3 })),
http.get('/api/backup/s3/status', () => HttpResponse.json({ Failed: false })),
http.get('/api/endpoint_groups', () => HttpResponse.json([])),
http.get('/api/endpoint_groups/:groupId', ({ params }) => {
if (params.groupId instanceof Array) {
throw new Error('should be string');
}
const id = parseInt(req.params.groupId, 10);
const id = parseInt(params.groupId, 10);
const group: Partial<EnvironmentGroup> = {
Id: id,
Name: `group${id}`,
};
return res(ctx.json(group));
return HttpResponse.json(group);
}),
rest.get('/api/tags', (req, res, ctx) => res(ctx.json(tags))),
rest.post<{ name: string }>('/api/tags', (req, res, ctx) => {
const tagName = req.body.name;
http.get('/api/tags', () => HttpResponse.json(tags)),
http.post<never, { name: string }>('/api/tags', async ({ request }) => {
const body = await request.json();
const tagName = body.name;
const tag = { ID: tags.length + 1, Name: tagName, Endpoints: {} };
tags.push(tag);
return res(ctx.json(tag));
return HttpResponse.json(tag);
}),
rest.get<DefaultBodyType, PathParams, Partial<PublicSettingsResponse>>(
http.get<never, never, Partial<PublicSettingsResponse>>(
'/api/settings/public',
(req, res, ctx) =>
res(
ctx.json({
() =>
HttpResponse.json({
Edge: {
AsyncMode: false,
CheckinInterval: 60,
@ -85,14 +81,10 @@ export const handlers = [
SnapshotInterval: 60,
},
})
)
),
rest.get<DefaultBodyType, PathParams, Partial<StatusResponse>>(
'/api/status',
(req, res, ctx) => res(ctx.json({}))
),
rest.get('/api/teams/:id/memberships', (req, res, ctx) => res(ctx.json([]))),
rest.get('/api/endpoints/agent_versions', (req, res, ctx) =>
res(ctx.json([]))
http.get<never, never, Partial<StatusResponse>>('/api/status', () =>
HttpResponse.json({})
),
http.get('/api/teams/:id/memberships', () => HttpResponse.json([])),
http.get('/api/endpoints/agent_versions', () => HttpResponse.json([])),
];

View File

@ -1,7 +1,7 @@
import { rest } from 'msw';
import { http } from 'msw';
import { setupServer } from 'msw/node';
import { handlers } from './server-handlers';
const server = setupServer(...handlers);
export { server, rest };
export { server, http };

View File

@ -1,9 +1,8 @@
import { rest } from 'msw';
import { http, HttpResponse } from 'msw';
export const azureHandlers = [
rest.get('/api/endpoints/:endpointId/azure/subscriptions', (req, res, ctx) =>
res(
ctx.json({
http.get('/api/endpoints/:endpointId/azure/subscriptions', () =>
HttpResponse.json({
value: [
{
id: '/subscriptions/sub1',
@ -14,14 +13,12 @@ export const azureHandlers = [
},
],
})
)
),
rest.get(
http.get(
'/api/endpoints/:endpointId/azure/subscriptions/:subscriptionId/providers/Microsoft.ContainerInstance',
(req, res, ctx) =>
res(
ctx.json({
id: `/subscriptions/${req.params.subscriptionId}/providers/Microsoft.ContainerInstance`,
({ params }) =>
HttpResponse.json({
id: `/subscriptions/${params.subscriptionId}/providers/Microsoft.ContainerInstance`,
namespace: 'Microsoft.ContainerInstance',
resourceTypes: [
{
@ -49,28 +46,25 @@ export const azureHandlers = [
},
],
})
)
),
rest.get(
http.get(
'/api/endpoints/:endpointId/azure/subscriptions/:subsriptionId/resourcegroups',
(res, req, ctx) =>
req(
ctx.json({
({ params }) =>
HttpResponse.json({
value: [
{
id: `/subscriptions/${res.params.subscriptionId}/resourceGroups/rg1`,
id: `/subscriptions/${params.subscriptionId}/resourceGroups/rg1`,
name: 'rg1',
location: 'southcentralus',
properties: { provisioningState: 'Succeeded' },
},
{
id: `/subscriptions/${res.params.subscriptionId}/resourceGroups/rg2`,
id: `/subscriptions/${params.subscriptionId}/resourceGroups/rg2`,
name: 'rg2',
location: 'southcentralus',
properties: { provisioningState: 'Succeeded' },
},
],
})
)
),
];

View File

@ -1,21 +1,19 @@
import { DefaultBodyType, PathParams, rest } from 'msw';
import { http, HttpResponse } from 'msw';
import { SystemInfo, SystemVersion } from 'docker-types/generated/1.41';
export const dockerHandlers = [
rest.get<DefaultBodyType, PathParams, SystemInfo>(
http.get<never, never, SystemInfo>(
'/api/endpoints/:endpointId/docker/info',
(req, res, ctx) =>
res(
ctx.json({
() =>
HttpResponse.json({
Plugins: { Authorization: [], Log: [], Network: [], Volume: [] },
MemTotal: 0,
NCPU: 0,
Runtimes: { runc: { path: 'runc' } },
})
)
),
rest.get<DefaultBodyType, PathParams, SystemVersion>(
http.get<never, never, SystemVersion>(
'/api/endpoints/:endpointId/docker/version',
(req, res, ctx) => res(ctx.json({ ApiVersion: '1.24' }))
() => HttpResponse.json({ ApiVersion: '1.24' })
),
];

View File

@ -1,14 +1,12 @@
import { DefaultBodyType, PathParams, rest } from 'msw';
import { http, HttpResponse } from 'msw';
import { TeamMembership } from '@/react/portainer/users/teams/types';
import { createMockUsers } from '@/react-tools/test-mocks';
export const userHandlers = [
rest.get('/api/users', async (req, res, ctx) =>
res(ctx.json(createMockUsers(10)))
),
rest.get<DefaultBodyType, PathParams, TeamMembership[]>(
http.get('/api/users', async () => HttpResponse.json(createMockUsers(10))),
http.get<never, never, TeamMembership[]>(
'/api/users/:userId/memberships',
(req, res, ctx) => res(ctx.json([]))
() => HttpResponse.json([])
),
];

View File

@ -134,7 +134,7 @@ module.exports = {
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
setupFiles: ['<rootDir>/app/setup-tests/jest-polyfills.ts'],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ['<rootDir>/app/setup-tests/setup-msw.ts'],

View File

@ -101,7 +101,7 @@
"lucide-react": "^0.101.0",
"moment": "^2.29.1",
"moment-timezone": "^0.5.40",
"msw": "^0.49.2",
"msw": "^2.0.11",
"mustache": "^4.2.0",
"ng-file-upload": "~12.2.13",
"parse-duration": "^1.0.2",
@ -209,6 +209,7 @@
"tailwindcss": "3.3.3",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"typescript": "^5.2.2",
"undici": "^6.2.1",
"webpack": "^5.88.2",
"webpack-build-notifier": "^2.3.0",
"webpack-bundle-analyzer": "^4.9.1",
@ -225,7 +226,8 @@
"http-proxy": "^1.18.1",
"**/@uirouter/react": "^1.0.7",
"**/@uirouter/angularjs": "1.0.11",
"**/moment": "^2.21.0"
"**/moment": "^2.21.0",
"msw/**/wrap-ansi": "^7.0.0"
},
"browserslist": "last 2 versions"
}

3815
yarn.lock

File diff suppressed because it is too large Load Diff