From 93349092d7e831f8ec1498d67eb81e7e564ff0a9 Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Mon, 16 Dec 2024 15:53:35 -0500 Subject: [PATCH] add MSW crud domains --- packages/manager/src/dev-tools/load.ts | 49 +--- .../features/Events/FormattedEventMessage.tsx | 5 +- .../src/features/Events/factories/domain.tsx | 14 +- packages/manager/src/mocks/mockState.ts | 2 + .../src/mocks/presets/baseline/crud.ts | 2 + .../manager/src/mocks/presets/crud/domains.ts | 24 ++ .../mocks/presets/crud/handlers/domains.ts | 252 ++++++++++++++++++ .../src/mocks/presets/crud/seeds/domains.ts | 32 +++ .../src/mocks/presets/crud/seeds/index.ts | 2 + .../src/mocks/presets/crud/seeds/utils.ts | 3 + packages/manager/src/mocks/types.ts | 12 +- .../manager/src/mocks/utilities/events.ts | 2 +- 12 files changed, 352 insertions(+), 47 deletions(-) create mode 100644 packages/manager/src/mocks/presets/crud/domains.ts create mode 100644 packages/manager/src/mocks/presets/crud/handlers/domains.ts create mode 100644 packages/manager/src/mocks/presets/crud/seeds/domains.ts diff --git a/packages/manager/src/dev-tools/load.ts b/packages/manager/src/dev-tools/load.ts index f77ea890b71..b8eb25c8a14 100644 --- a/packages/manager/src/dev-tools/load.ts +++ b/packages/manager/src/dev-tools/load.ts @@ -74,7 +74,7 @@ export async function loadDevTools( seedContext[key] = seeds[key]; }; - const seeds = await populateSeeds(emptyStore); + const seeds = await populateSeeds(seedContext); const seedPromises = (Object.keys( seedContext @@ -84,44 +84,19 @@ export async function loadDevTools( await mswDB.saveStore(seedContext ?? emptyStore, 'seedState'); - // Merge the contexts + const stateKeys: (keyof MockState)[] = Object.keys( + emptyStore + ) as (keyof MockState)[]; + const mergedContext: MockState = { ...initialContext, - eventQueue: [ - ...initialContext.eventQueue, - ...(seedContext?.eventQueue || []), - ], - firewalls: [ - ...initialContext.firewalls, - ...(seedContext?.firewalls || []), - ], - linodeConfigs: [ - ...initialContext.linodeConfigs, - ...(seedContext?.linodeConfigs || []), - ], - linodes: [...initialContext.linodes, ...(seedContext?.linodes || [])], - notificationQueue: [ - ...initialContext.notificationQueue, - ...(seedContext?.notificationQueue || []), - ], - placementGroups: [ - ...initialContext.placementGroups, - ...(seedContext?.placementGroups || []), - ], - regionAvailability: [ - ...initialContext.regionAvailability, - ...(seedContext?.regionAvailability || []), - ], - regions: [...initialContext.regions, ...(seedContext?.regions || [])], - supportReplies: [ - ...initialContext.supportReplies, - ...(seedContext?.supportReplies || []), - ], - supportTickets: [ - ...initialContext.supportTickets, - ...(seedContext?.supportTickets || []), - ], - volumes: [...initialContext.volumes, ...(seedContext?.volumes || [])], + ...stateKeys.reduce( + (acc, key) => ({ + ...acc, + [key]: [...initialContext[key], ...(seedContext?.[key] || [])], + }), + {} + ), }; const extraHandlers = extraMswPresets.reduce( diff --git a/packages/manager/src/features/Events/FormattedEventMessage.tsx b/packages/manager/src/features/Events/FormattedEventMessage.tsx index f378c933be1..2619bfbd121 100644 --- a/packages/manager/src/features/Events/FormattedEventMessage.tsx +++ b/packages/manager/src/features/Events/FormattedEventMessage.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import { SupportLink } from 'src/components/SupportLink'; interface MessageLinkEntity { + fallback?: string; message: null | string; } @@ -15,10 +16,10 @@ interface MessageLinkEntity { * - render "contact support" strings as . */ export const FormattedEventMessage = (props: MessageLinkEntity) => { - const { message } = props; + const { fallback, message } = props; if (!message) { - return null; + return fallback ? fallback : null; } return formatMessage(message); diff --git a/packages/manager/src/features/Events/factories/domain.tsx b/packages/manager/src/features/Events/factories/domain.tsx index c29d2e95c27..61377e97427 100644 --- a/packages/manager/src/features/Events/factories/domain.tsx +++ b/packages/manager/src/features/Events/factories/domain.tsx @@ -30,12 +30,14 @@ export const domain: PartialEventMap<'domain'> = { ), }, domain_record_create: { - notification: (e) => ( - <> - has been{' '} - added to . - - ), + notification: (e) => { + return ( + <> + has + been added to . + + ); + }, }, domain_record_delete: { notification: (e) => ( diff --git a/packages/manager/src/mocks/mockState.ts b/packages/manager/src/mocks/mockState.ts index d4aa0d3a905..5d95b9f3f46 100644 --- a/packages/manager/src/mocks/mockState.ts +++ b/packages/manager/src/mocks/mockState.ts @@ -22,6 +22,8 @@ export const getStateSeederGroups = ( }; export const emptyStore: MockState = { + domainRecords: [], + domains: [], eventQueue: [], firewalls: [], linodeConfigs: [], diff --git a/packages/manager/src/mocks/presets/baseline/crud.ts b/packages/manager/src/mocks/presets/baseline/crud.ts index 1719f7772fd..12679014c81 100644 --- a/packages/manager/src/mocks/presets/baseline/crud.ts +++ b/packages/manager/src/mocks/presets/baseline/crud.ts @@ -4,6 +4,7 @@ import { } from 'src/mocks/presets/crud/handlers/events'; import { linodeCrudPreset } from 'src/mocks/presets/crud/linodes'; +import { domainCrudPreset } from '../crud/domains'; import { placementGroupsCrudPreset } from '../crud/placementGroups'; import { supportTicketCrudPreset } from '../crud/supportTickets'; import { volumeCrudPreset } from '../crud/volumes'; @@ -17,6 +18,7 @@ export const baselineCrudPreset: MockPresetBaseline = { ...placementGroupsCrudPreset.handlers, ...supportTicketCrudPreset.handlers, ...volumeCrudPreset.handlers, + ...domainCrudPreset.handlers, // Events. getEvents, diff --git a/packages/manager/src/mocks/presets/crud/domains.ts b/packages/manager/src/mocks/presets/crud/domains.ts new file mode 100644 index 00000000000..58a2d136617 --- /dev/null +++ b/packages/manager/src/mocks/presets/crud/domains.ts @@ -0,0 +1,24 @@ +import { + cloneDomain, + createDomain, + deleteDomains, + getDomains, + importDomain, + updateDomain, +} from 'src/mocks/presets/crud/handlers/domains'; + +import type { MockPresetCrud } from 'src/mocks/types'; + +export const domainCrudPreset: MockPresetCrud = { + group: { id: 'Domains' }, + handlers: [ + createDomain, + deleteDomains, + updateDomain, + getDomains, + cloneDomain, + importDomain, + ], + id: 'domains:crud', + label: 'Domains CRUD', +}; diff --git a/packages/manager/src/mocks/presets/crud/handlers/domains.ts b/packages/manager/src/mocks/presets/crud/handlers/domains.ts new file mode 100644 index 00000000000..4c14bd4152b --- /dev/null +++ b/packages/manager/src/mocks/presets/crud/handlers/domains.ts @@ -0,0 +1,252 @@ +import { DateTime } from 'luxon'; +import { http } from 'msw'; + +import { domainFactory, domainRecordFactory } from 'src/factories'; +import { mswDB } from 'src/mocks/indexedDB'; +import { queueEvents } from 'src/mocks/utilities/events'; +import { + makeNotFoundResponse, + makePaginatedResponse, + makeResponse, +} from 'src/mocks/utilities/response'; + +import type { Domain, DomainRecord } from '@linode/api-v4'; +import type { StrictResponse } from 'msw'; +import type { MockState } from 'src/mocks/types'; +import type { + APIErrorResponse, + APIPaginatedResponse, +} from 'src/mocks/utilities/response'; + +export const getDomains = () => [ + http.get( + '*/v4/domains/:id/records', + async ({ + request, + }): Promise< + StrictResponse> + > => { + const domainRecords = domainRecordFactory.buildList(3); + + return makePaginatedResponse({ + data: domainRecords, + request, + }); + } + ), + + http.get( + '*/v4/domains', + async ({ + request, + }): Promise< + StrictResponse> + > => { + const domains = await mswDB.getAll('domains'); + + if (!domains) { + return makeNotFoundResponse(); + } + + return makePaginatedResponse({ + data: domains, + request, + }); + } + ), + + http.get( + '*/v4/domains/:id', + async ({ params }): Promise> => { + const id = Number(params.id); + const domain = await mswDB.get('domains', id); + + if (!domain) { + return makeNotFoundResponse(); + } + + return makeResponse(domain); + } + ), +]; + +export const createDomain = (mockState: MockState) => [ + http.post( + '*/v4/domains', + async ({ request }): Promise> => { + const payload = await request.clone().json(); + + const domain = domainFactory.build({ + ...payload, + created: DateTime.now().toISO(), + last_updated: DateTime.now().toISO(), + updated: DateTime.now().toISO(), + }); + + await mswDB.add('domains', domain, mockState); + + queueEvents({ + event: { + action: 'domain_create', + entity: { + id: domain.id, + label: domain.domain, + type: 'domain', + url: `/v4/domains/${domain.id}`, + }, + }, + mockState, + sequence: [{ status: 'notification' }], + }); + + return makeResponse(domain); + } + ), +]; + +export const updateDomain = (mockState: MockState) => [ + http.put( + '*/v4/domains/:id', + async ({ + params, + request, + }): Promise> => { + const id = Number(params.id); + const domain = await mswDB.get('domains', id); + + if (!domain) { + return makeNotFoundResponse(); + } + + const payload = { + ...(await request.clone().json()), + last_updated: DateTime.now().toISO(), + updated: DateTime.now().toISO(), + }; + const updatedDomain = { ...domain, ...payload }; + + await mswDB.update('domains', id, updatedDomain, mockState); + + queueEvents({ + event: { + action: 'domain_update', + entity: { + id: domain.id, + label: domain.domain, + type: 'domain', + url: `/v4/domains/${domain.id}`, + }, + }, + mockState, + sequence: [{ status: 'notification' }], + }); + + return makeResponse(updatedDomain); + } + ), +]; + +export const cloneDomain = (mockState: MockState) => [ + http.post( + '*/v4/domains/:id/clone', + async ({ params }): Promise> => { + const id = Number(params.id); + const domain = await mswDB.get('domains', id); + + if (!domain) { + return makeNotFoundResponse(); + } + + const clonedDomain = { + ...domain, + created: DateTime.now().toISO(), + last_updated: DateTime.now().toISO(), + updated: DateTime.now().toISO(), + }; + + await mswDB.add('domains', clonedDomain, mockState); + + queueEvents({ + event: { + action: 'domain_create', + entity: { + id: domain.id, + label: domain.domain, + type: 'domain', + url: `/v4/domains/${domain.id}`, + }, + }, + mockState, + sequence: [{ status: 'notification' }], + }); + + return makeResponse(domain); + } + ), +]; + +export const importDomain = (mockState: MockState) => [ + http.post( + '*/v4/domains/import', + async ({ request }): Promise> => { + const payload = await request.clone().json(); + + const domain = domainFactory.build({ + ...payload, + created: DateTime.now().toISO(), + last_updated: DateTime.now().toISO(), + updated: DateTime.now().toISO(), + }); + + await mswDB.add('domains', domain, mockState); + + queueEvents({ + event: { + action: 'domain_create', + entity: { + id: domain.id, + label: domain.domain, + type: 'domain', + url: `/v4/domains/${domain.id}`, + }, + }, + mockState, + sequence: [{ status: 'notification' }], + }); + + return makeResponse(domain); + } + ), +]; + +export const deleteDomains = (mockState: MockState) => [ + http.delete( + '*/v4/domains/:id', + async ({ params }): Promise> => { + const id = Number(params.id); + const domain = await mswDB.get('domains', id); + + if (!domain) { + return makeNotFoundResponse(); + } + + await mswDB.delete('domains', id, mockState); + + queueEvents({ + event: { + action: 'domain_delete', + entity: { + id: domain.id, + label: domain.domain, + type: 'domain', + url: `/v4/domains/${domain.id}`, + }, + }, + mockState, + sequence: [{ status: 'notification' }], + }); + + return makeResponse({}); + } + ), +]; diff --git a/packages/manager/src/mocks/presets/crud/seeds/domains.ts b/packages/manager/src/mocks/presets/crud/seeds/domains.ts new file mode 100644 index 00000000000..989aa240f83 --- /dev/null +++ b/packages/manager/src/mocks/presets/crud/seeds/domains.ts @@ -0,0 +1,32 @@ +import { getSeedsCountMap } from 'src/dev-tools/utils'; +import { domainFactory } from 'src/factories'; +import { mswDB } from 'src/mocks/indexedDB'; +import { seedWithUniqueIds } from 'src/mocks/presets/crud/seeds/utils'; + +import type { MockSeeder, MockState } from 'src/mocks/types'; + +export const domainSeeder: MockSeeder = { + canUpdateCount: true, + desc: 'Domains Seeds', + group: { id: 'Domains' }, + id: 'domains:crud', + label: 'Domains', + + seeder: async (mockState: MockState) => { + const seedsCountMap = getSeedsCountMap(); + const count = seedsCountMap[domainSeeder.id] ?? 0; + const domainSeeds = seedWithUniqueIds<'domains'>({ + dbEntities: await mswDB.getAll('domains'), + seedEntities: domainFactory.buildList(count), + }); + + const updatedMockState = { + ...mockState, + domains: mockState.domains.concat(domainSeeds), + }; + + await mswDB.saveStore(updatedMockState, 'seedState'); + + return updatedMockState; + }, +}; diff --git a/packages/manager/src/mocks/presets/crud/seeds/index.ts b/packages/manager/src/mocks/presets/crud/seeds/index.ts index f00084e08bc..3aab667a0e3 100644 --- a/packages/manager/src/mocks/presets/crud/seeds/index.ts +++ b/packages/manager/src/mocks/presets/crud/seeds/index.ts @@ -1,9 +1,11 @@ +import { domainSeeder } from './domains'; import { linodesSeeder } from './linodes'; import { placementGroupSeeder } from './placementGroups'; import { supportTicketsSeeder } from './supportTickets'; import { volumesSeeder } from './volumes'; export const dbSeeders = [ + domainSeeder, linodesSeeder, placementGroupSeeder, supportTicketsSeeder, diff --git a/packages/manager/src/mocks/presets/crud/seeds/utils.ts b/packages/manager/src/mocks/presets/crud/seeds/utils.ts index 5b77c2d2781..8fe4f536c89 100644 --- a/packages/manager/src/mocks/presets/crud/seeds/utils.ts +++ b/packages/manager/src/mocks/presets/crud/seeds/utils.ts @@ -13,6 +13,9 @@ import type { MockSeeder, MockState } from 'src/mocks/types'; */ export const removeSeeds = async (seederId: MockSeeder['id']) => { switch (seederId) { + case 'domains:crud': + await mswDB.deleteAll('domains', mockState, 'seedState'); + break; case 'linodes:crud': await mswDB.deleteAll('linodes', mockState, 'seedState'); await mswDB.deleteAll('linodeConfigs', mockState, 'seedState'); diff --git a/packages/manager/src/mocks/types.ts b/packages/manager/src/mocks/types.ts index 5b01ed9834e..7597a52cd2f 100644 --- a/packages/manager/src/mocks/types.ts +++ b/packages/manager/src/mocks/types.ts @@ -1,5 +1,7 @@ import type { Config, + Domain, + DomainRecord, Event, Firewall, Linode, @@ -79,9 +81,15 @@ export interface MockPresetExtra extends MockPresetBase { * Mock Preset Crud */ export type MockPresetCrudGroup = { - id: 'Linodes' | 'Placement Groups' | 'Support Tickets' | 'Volumes'; + id: + | 'Domains' + | 'Linodes' + | 'Placement Groups' + | 'Support Tickets' + | 'Volumes'; }; export type MockPresetCrudId = + | 'domains:crud' | 'linodes:crud' | 'placement-groups:crud' | 'support-tickets:crud' @@ -98,6 +106,8 @@ export type MockHandler = (mockState: MockState) => HttpHandler[]; * Stateful data shared among mocks. */ export interface MockState { + domainRecords: DomainRecord[]; + domains: Domain[]; eventQueue: Event[]; firewalls: Firewall[]; linodeConfigs: [number, Config][]; diff --git a/packages/manager/src/mocks/utilities/events.ts b/packages/manager/src/mocks/utilities/events.ts index 5c8b24a4286..f5c7bf4973d 100644 --- a/packages/manager/src/mocks/utilities/events.ts +++ b/packages/manager/src/mocks/utilities/events.ts @@ -28,7 +28,7 @@ const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export const queueEvents = (props: QueuedEvents): Promise => { const { event, mockState, sequence } = props; - const initialDelay = 7500; + const initialDelay = 2500; const progressDelay = 10_000; let accumulatedDelay = 0; let lastEventWasProgress = false;