From ac0b94e306d878f55bc9bade4591f868d039606a Mon Sep 17 00:00:00 2001 From: Yoann Fievez Date: Thu, 17 Oct 2024 16:28:17 +0200 Subject: [PATCH] test(pci-load-balancer): add unit tests ref: DTCORE-2770 Signed-off-by: Yoann Fievez --- .../new/__snapshots__/New.page.spec.tsx.snap | 186 -------- .../apps/pci-load-balancer/package.json | 5 +- .../src/api/data/flavors.spec.ts | 47 ++ .../src/api/data/floating-ips.spec.ts | 46 ++ .../src/api/data/gateways.spec.ts | 62 +++ .../src/api/data/health-monitor.spec.ts | 133 ++++++ .../src/{types/index.ts => api/data/jwt.go} | 0 .../src/api/data/l7Policies.spec.ts | 102 ++++ .../src/api/data/l7Rules.spec.ts | 95 ++++ .../src/api/data/listener.spec.ts | 37 ++ .../src/api/data/load-balancer.spec.ts | 359 +++++++++++++- .../src/api/data/load-balancer.ts | 4 +- .../src/api/data/network.spec.ts | 199 ++++++++ .../src/api/data/plans.spec.ts | 46 ++ .../src/api/data/pool-member.spec.ts | 102 ++++ .../src/api/data/pool.spec.ts | 202 ++++++++ .../pci-load-balancer/src/api/data/pool.ts | 1 + .../src/api/hook/useAddons.spec.ts | 95 ++++ .../src/api/hook/useFlavors.spec.ts | 34 ++ .../src/api/hook/useFloatingIps.spec.ts | 25 + .../src/api/hook/useGateways.spec.ts | 34 ++ .../src/api/hook/useHealthMonitor.spec.tsx | 159 +++++++ .../src/api/hook/useL7Policy.spec.tsx | 316 ++++++++++++ .../src/api/hook/useL7Policy.tsx | 26 +- .../src/api/hook/useL7Rule.spec.tsx | 214 +++++++++ .../src/api/hook/useL7Rule.tsx | 2 +- .../src/api/hook/useListener.spec.tsx | 89 ++++ .../src/api/hook/useLoadBalancer.spec.ts | 449 ++++++++++++++++-- .../src/api/hook/useNetwork.spec.tsx | 104 ++++ .../src/api/hook/usePlans.spec.ts | 30 ++ .../src/api/hook/usePool.spec.tsx | 175 +++++++ .../src/api/hook/usePoolMember.spec.tsx | 220 +++++++++ .../src/api/hook/useRegions.spec.ts | 81 ++++ .../components/form/Label.component.spec.tsx | 37 ++ .../form/PolicyForm.component.spec.tsx | 62 +++ .../components/form/PolicyForm.component.tsx | 5 +- .../components/form/RuleForm.component.tsx | 3 +- .../form/Ruleform.component.spec.tsx | 39 ++ .../src/helpers/index.spec.ts | 4 +- .../apps/pci-load-balancer/src/mocks/index.ts | 46 +- .../apps/pci-load-balancer/src/setupTests.tsx | 15 + .../apps/pci-load-balancer/vitest.config.js | 4 +- .../__snapshots__/PlanStep.spec.tsx.snap | 8 +- .../QuantitySelector.component.tsx | 4 +- 44 files changed, 3624 insertions(+), 282 deletions(-) delete mode 100644 packages/manager/apps/pci-kubernetes/src/pages/detail/nodepools/new/__snapshots__/New.page.spec.tsx.snap create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/flavors.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/floating-ips.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/gateways.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/health-monitor.spec.ts rename packages/manager/apps/pci-load-balancer/src/{types/index.ts => api/data/jwt.go} (100%) create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/l7Policies.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/l7Rules.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/listener.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/network.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/plans.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/pool-member.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/data/pool.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useAddons.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useFlavors.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useFloatingIps.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useGateways.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useHealthMonitor.spec.tsx create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useL7Policy.spec.tsx create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useL7Rule.spec.tsx create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useListener.spec.tsx create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useNetwork.spec.tsx create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/usePlans.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/usePool.spec.tsx create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/usePoolMember.spec.tsx create mode 100644 packages/manager/apps/pci-load-balancer/src/api/hook/useRegions.spec.ts create mode 100644 packages/manager/apps/pci-load-balancer/src/components/form/Label.component.spec.tsx create mode 100644 packages/manager/apps/pci-load-balancer/src/components/form/PolicyForm.component.spec.tsx create mode 100644 packages/manager/apps/pci-load-balancer/src/components/form/Ruleform.component.spec.tsx diff --git a/packages/manager/apps/pci-kubernetes/src/pages/detail/nodepools/new/__snapshots__/New.page.spec.tsx.snap b/packages/manager/apps/pci-kubernetes/src/pages/detail/nodepools/new/__snapshots__/New.page.spec.tsx.snap deleted file mode 100644 index ccd70112770c..000000000000 --- a/packages/manager/apps/pci-kubernetes/src/pages/detail/nodepools/new/__snapshots__/New.page.spec.tsx.snap +++ /dev/null @@ -1,186 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`New > should render correctly 1`] = ` -
- - kube_common_create_node_pool - -
-
-
-
- - 1 - -
-
-
-
-
- kube_add_node_pool_config_title -
-
-
- - - kube_add_node_pool_name_label - - - -
-
- - common_stepper_next_button_label - -
-
-
-
-
-
-
- - 2 - -
-
-
-
-
- kube_common_node_pool_title -
-
-
-
-
-
-
-
- - 3 - -
-
-
-
-
- kube_common_node_pool_autoscaling_title -
-
-
-
-
-
-
-
- - 4 - -
-
-
-
-
- kube_add_billing_anti_affinity_title -
-
-
-
-
-`; diff --git a/packages/manager/apps/pci-load-balancer/package.json b/packages/manager/apps/pci-load-balancer/package.json index c1fdd0f11bdb..74910f0e90c4 100644 --- a/packages/manager/apps/pci-load-balancer/package.json +++ b/packages/manager/apps/pci-load-balancer/package.json @@ -12,7 +12,10 @@ "start": "lerna exec --stream --scope='@ovh-ux/manager-pci-load-balancer-app' --include-dependencies -- npm run build --if-present", "start:dev": "lerna exec --stream --scope='@ovh-ux/manager-pci-load-balancer-app' --include-dependencies -- npm run dev --if-present", "start:watch": "lerna exec --stream --parallel --scope='@ovh-ux/manager-pci-load-balancer-app' --include-dependencies -- npm run dev:watch --if-present", - "test": "vitest run" + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "test:watch": "vitest", + "test:watch:coverage": "vitest --coverage" }, "dependencies": { "@ovh-ux/manager-config": "*", diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/flavors.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/flavors.spec.ts new file mode 100644 index 000000000000..6b50402c8d10 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/flavors.spec.ts @@ -0,0 +1,47 @@ +import { describe, it, expect, vi } from 'vitest'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { TAddon } from '@/pages/create/store'; +import { getFlavor } from './flavors'; +import { TFlavor } from '@/api/data/load-balancer'; + +describe('getFlavor', () => { + const projectId = 'test-project'; + const regionName = 'test-region'; + const addon = ({ + technicalName: 'test-flavor', + invoiceName: 'invoiceName', + } as unknown) as TAddon; + + it('should return the correct flavor', async () => { + const mockFlavors: TFlavor[] = [ + { name: 'test-flavor', region: 'region1', id: 'id1' }, + { name: 'another-flavor', region: 'region2', id: 'id2' }, + ]; + + vi.mocked(v6.get).mockResolvedValue({ data: mockFlavors }); + + const result = await getFlavor(projectId, regionName, addon); + + expect(result).toEqual(mockFlavors[0]); + }); + + it('should return undefined if the flavor is not found', async () => { + const mockFlavors: TFlavor[] = [ + { name: 'another-flavor', region: 'value2', id: 'id2' }, + ]; + + vi.mocked(v6.get).mockResolvedValue({ data: mockFlavors }); + + const result = await getFlavor(projectId, regionName, addon); + + expect(result).toBeUndefined(); + }); + + it('should handle API errors gracefully', async () => { + vi.mocked(v6.get).mockRejectedValue(new Error('API Error')); + + await expect(getFlavor(projectId, regionName, addon)).rejects.toThrow( + 'API Error', + ); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/floating-ips.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/floating-ips.spec.ts new file mode 100644 index 000000000000..f8e5db1cae99 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/floating-ips.spec.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, vi } from 'vitest'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { getFloatingIps, TFloatingIp } from './floating-ips'; + +describe('getFloatingIps', () => { + const projectId = 'test-project-id'; + const region = 'test-region'; + const mockFloatingIps: TFloatingIp[] = [ + { + associatedEntity: 'entity1', + id: 'id1', + ip: '192.168.1.1', + networkId: 'network1', + status: 'active', + type: 'public', + }, + { + associatedEntity: 'entity2', + id: 'id2', + ip: '192.168.1.2', + networkId: 'network2', + status: 'inactive', + type: 'private', + }, + ]; + + it('should fetch floating IPs for a given project and region', async () => { + vi.mocked(v6.get).mockResolvedValue({ data: mockFloatingIps }); + + const result = await getFloatingIps(projectId, region); + + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/floatingip`, + ); + expect(result).toEqual(mockFloatingIps); + }); + + it('should handle errors when fetching floating IPs', async () => { + const errorMessage = 'Network Error'; + vi.mocked(v6.get).mockRejectedValue(new Error(errorMessage)); + + await expect(getFloatingIps(projectId, region)).rejects.toThrow( + errorMessage, + ); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/gateways.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/gateways.spec.ts new file mode 100644 index 000000000000..0dda4d74294d --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/gateways.spec.ts @@ -0,0 +1,62 @@ +import { describe, it, expect, vi } from 'vitest'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { getSubnetGateways, TSubnetGateway } from './gateways'; + +describe('getSubnetGateways', () => { + const projectId = 'test-project'; + const regionName = 'test-region'; + const subnetId = 'test-subnet'; + + const mockGateways: TSubnetGateway[] = [ + { + externalInformation: { + ips: [{ ip: '192.168.1.1', subnetId: 'subnet-1' }], + networkId: 'network-1', + }, + id: 'gateway-1', + interfaces: [ + { + id: 'interface-1', + ip: '192.168.1.2', + networkId: 'network-1', + subnetId: 'subnet-1', + }, + ], + model: 'model-1', + name: 'gateway-name-1', + region: 'region-1', + status: 'ACTIVE', + }, + ]; + + it('should fetch subnet gateways and return data', async () => { + vi.mocked(v6.get).mockResolvedValueOnce({ data: mockGateways }); + + const result = await getSubnetGateways(projectId, regionName, subnetId); + + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${regionName}/gateway?subnetId=${subnetId}`, + ); + expect(result).toEqual(mockGateways); + }); + + it('should handle empty response', async () => { + vi.mocked(v6.get).mockResolvedValueOnce({ data: [] }); + + const result = await getSubnetGateways(projectId, regionName, subnetId); + + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${regionName}/gateway?subnetId=${subnetId}`, + ); + expect(result).toEqual([]); + }); + + it('should handle API errors', async () => { + const errorMessage = 'API Error'; + vi.mocked(v6.get).mockRejectedValueOnce(new Error(errorMessage)); + + await expect( + getSubnetGateways(projectId, regionName, subnetId), + ).rejects.toThrow(errorMessage); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/health-monitor.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/health-monitor.spec.ts new file mode 100644 index 000000000000..94c633d2d540 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/health-monitor.spec.ts @@ -0,0 +1,133 @@ +import { describe, it, expect, vi } from 'vitest'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { + getHealthMonitor, + deleteHealthMonitor, + createHealthMonitor, + editHealthMonitor, + renameHealthMonitor, + THealthMonitorFormState, +} from './health-monitor'; + +describe('Health Monitor API', () => { + const projectId = 'test-project'; + const region = 'test-region'; + const poolId = 'test-pool'; + const healthMonitorId = 'test-health-monitor'; + + it('should get health monitor', async () => { + const mockData = [{ id: '1', name: 'test-monitor' }]; + vi.mocked(v6.get).mockResolvedValue({ data: mockData }); + + const result = await getHealthMonitor(projectId, region, poolId); + expect(result).toEqual(mockData); + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/healthMonitor?poolId=${poolId}`, + ); + }); + + it('should delete health monitor', async () => { + const mockData = { success: true }; + vi.mocked(v6.delete).mockResolvedValue({ data: mockData }); + + const result = await deleteHealthMonitor( + projectId, + region, + healthMonitorId, + ); + expect(result).toEqual(mockData); + expect(v6.delete).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/healthMonitor/${healthMonitorId}`, + ); + }); + + it('should create health monitor', async () => { + const model: THealthMonitorFormState = { + name: 'test-monitor', + type: 'http', + urlPath: '/health', + expectedCode: 200, + maxRetriesDown: 3, + delay: 5, + maxRetries: 3, + timeout: 10, + }; + const mockData = { id: '1', name: 'test-monitor' }; + (v6.post as any).mockResolvedValue({ data: mockData }); + + const result = await createHealthMonitor(projectId, region, poolId, model); + expect(result).toEqual({ data: mockData }); + expect(v6.post).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/healthMonitor`, + expect.objectContaining({ + name: model.name, + monitorType: model.type, + delay: model.delay, + timeout: model.timeout, + poolId, + httpConfiguration: expect.objectContaining({ + expectedCodes: `${model.expectedCode}`, + urlPath: model.urlPath, + }), + }), + ); + }); + + it('should edit health monitor', async () => { + const model: THealthMonitorFormState = { + name: 'updated-monitor', + urlPath: '/health', + expectedCode: 200, + maxRetriesDown: 3, + delay: 5, + maxRetries: 3, + timeout: 10, + }; + const mockData = { id: '1', name: 'updated-monitor' }; + vi.mocked(v6.put).mockResolvedValue({ data: mockData }); + + const result = await editHealthMonitor( + projectId, + region, + healthMonitorId, + model, + ); + expect(result).toEqual({ data: mockData }); + expect(v6.put).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/healthMonitor/${healthMonitorId}`, + { + name: model.name, + delay: model.delay, + timeout: model.timeout, + maxRetries: 3, + maxRetriesDown: 3, + httpConfiguration: { + expectedCodes: `${model.expectedCode}`, + httpMethod: 'GET', + httpVersion: '1.0', + urlPath: model.urlPath, + }, + }, + ); + }); + + it('should rename health monitor', async () => { + const newName = 'renamed-monitor'; + const mockData = { id: '1', name: newName }; + vi.mocked(v6.put).mockResolvedValue({ data: mockData }); + + const result = await renameHealthMonitor( + projectId, + region, + healthMonitorId, + newName, + ); + expect(result).toEqual({ data: mockData }); + expect( + v6.put, + ).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/healthMonitor/${healthMonitorId}`, + { name: newName }, + ); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/types/index.ts b/packages/manager/apps/pci-load-balancer/src/api/data/jwt.go similarity index 100% rename from packages/manager/apps/pci-load-balancer/src/types/index.ts rename to packages/manager/apps/pci-load-balancer/src/api/data/jwt.go diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/l7Policies.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/l7Policies.spec.ts new file mode 100644 index 000000000000..1e2fb9ec8c6d --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/l7Policies.spec.ts @@ -0,0 +1,102 @@ +import { describe, it, expect } from 'vitest'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { + getL7Policies, + getPolicy, + deletePolicy, + createPolicy, + updatePolicy, + TL7Policy, +} from './l7Policies'; +import { + LoadBalancerOperatingStatusEnum, + LoadBalancerProvisioningStatusEnum, +} from './load-balancer'; + +describe('l7Policies API', () => { + const projectId = 'test-project'; + const region = 'test-region'; + const listenerId = 'test-listener'; + const policyId = 'test-policy'; + const policy: TL7Policy = { + id: 'test-policy', + name: 'Test Policy', + description: 'Test Description', + operatingStatus: LoadBalancerOperatingStatusEnum.ONLINE, + provisioningStatus: LoadBalancerProvisioningStatusEnum.ACTIVE, + redirectHttpCode: 301, + redirectPoolId: null, + redirectPrefix: null, + redirectUrl: null, + action: 'REDIRECT_TO_URL', + position: 1, + listenerId: 'test-listener', + }; + + it('should fetch L7 policies', async () => { + const mockData = [policy]; + (v6.get as any).mockResolvedValue({ data: mockData }); + + const result = await getL7Policies(projectId, listenerId, region); + expect(result).toEqual(mockData); + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy?listenerId=${listenerId}`, + ); + }); + + it('should fetch a single policy', async () => { + (v6.get as any).mockResolvedValue({ data: policy }); + + const result = await getPolicy(projectId, region, policyId); + expect(result).toEqual(policy); + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy/${policyId}`, + ); + }); + + it('should delete a policy', async () => { + const mockData = { success: true }; + (v6.delete as any).mockResolvedValue({ data: mockData }); + + const result = await deletePolicy(projectId, region, policyId); + expect(result).toEqual(mockData); + expect(v6.delete).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy/${policyId}`, + ); + }); + + it('should create a policy', async () => { + const mockData = { id: 'new-policy' }; + (v6.post as any).mockResolvedValue({ data: mockData }); + + const result = await createPolicy(projectId, region, listenerId, policy); + expect(result).toEqual(mockData); + expect(v6.post).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy`, + { + listenerId, + ...policy, + }, + ); + }); + + it('should update a policy', async () => { + const mockData = { success: true }; + (v6.put as any).mockResolvedValue({ data: mockData }); + + const result = await updatePolicy(projectId, region, policy); + expect(result).toEqual(mockData); + expect(v6.put).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy/${policy.id}`, + { + name: policy.name, + position: policy.position, + action: policy.action, + redirectHttpCode: policy.redirectHttpCode, + redirectPoolId: policy.redirectPoolId, + redirectPrefix: policy.redirectPrefix, + redirectUrl: policy.redirectUrl, + }, + ); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/l7Rules.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/l7Rules.spec.ts new file mode 100644 index 000000000000..c0912c408398 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/l7Rules.spec.ts @@ -0,0 +1,95 @@ +import { describe, it, expect } from 'vitest'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { + getL7Rules, + deleteL7Rule, + createL7Rule, + updateL7Rule, + getL7Rule, + TL7Rule, +} from './l7Rules'; +import { + LoadBalancerOperatingStatusEnum, + LoadBalancerProvisioningStatusEnum, +} from './load-balancer'; + +describe('L7 Rules API', () => { + const projectId = 'test-project'; + const region = 'test-region'; + const policyId = 'test-policy'; + const ruleId = 'test-rule'; + const rule: TL7Rule = { + id: ruleId, + operatingStatus: LoadBalancerOperatingStatusEnum.ONLINE, + provisioningStatus: LoadBalancerProvisioningStatusEnum.ACTIVE, + key: 'test-key', + value: 'test-value', + invert: false, + ruleType: 'HOST_NAME', + compareType: 'EQUAL_TO', + }; + + it('should get L7 rules', async () => { + const mockData = [rule]; + (v6.get as any).mockResolvedValue({ data: mockData }); + + const result = await getL7Rules(projectId, region, policyId); + expect(result).toEqual(mockData); + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy/${policyId}/l7Rule`, + ); + }); + + it('should delete an L7 rule', async () => { + const mockData = {}; + (v6.delete as any).mockResolvedValue({ data: mockData }); + + const result = await deleteL7Rule(projectId, region, policyId, ruleId); + expect(result).toEqual(mockData); + expect(v6.delete).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy/${policyId}/l7Rule/${ruleId}`, + ); + }); + + it('should create an L7 rule', async () => { + const mockData = rule; + (v6.post as any).mockResolvedValue({ data: mockData }); + + const result = await createL7Rule(projectId, region, policyId, rule); + expect(result).toEqual(mockData); + expect(v6.post).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy/${policyId}/l7Rule`, + rule, + ); + }); + + it('should update an L7 rule', async () => { + const updatedRule = { ...rule, value: 'updated-value' }; + const mockData = updatedRule; + (v6.put as any).mockResolvedValue({ data: mockData }); + + const result = await updateL7Rule(projectId, region, policyId, updatedRule); + expect(result).toEqual(mockData); + expect(v6.put).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy/${policyId}/l7Rule/${rule.id}`, + { + ruleType: updatedRule.ruleType, + compareType: updatedRule.compareType, + key: updatedRule.key, + value: updatedRule.value, + invert: updatedRule.invert, + }, + ); + }); + + it('should get a specific L7 rule', async () => { + const mockData = rule; + (v6.get as any).mockResolvedValue({ data: mockData }); + + const result = await getL7Rule(projectId, region, policyId, ruleId); + expect(result).toEqual(mockData); + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/l7Policy/${policyId}/l7Rule/${ruleId}`, + ); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/listener.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/listener.spec.ts new file mode 100644 index 000000000000..9b1d06b7cbed --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/listener.spec.ts @@ -0,0 +1,37 @@ +import { describe, it, expect, vi } from 'vitest'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { deleteListener, getListener } from './listener'; + +describe('listener API', () => { + const projectId = 'test-project'; + const region = 'test-region'; + const listenerId = 'test-listener'; + + describe('deleteListener', () => { + it('should delete a listener and return data', async () => { + const mockResponse = { success: true }; + vi.mocked(v6.delete).mockResolvedValueOnce({ data: mockResponse }); + + const result = await deleteListener(projectId, region, listenerId); + + expect(v6.delete).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/listener/${listenerId}`, + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('getListener', () => { + it('should get a listener and return data', async () => { + const mockListener = { id: listenerId, name: 'test-listener' }; + vi.mocked(v6.get).mockResolvedValueOnce({ data: mockListener }); + + const result = await getListener(projectId, region, listenerId); + + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/listener/${listenerId}`, + ); + expect(result).toEqual(mockListener); + }); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.spec.ts index d6ce6b6e2c14..4555e6c3a93f 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.spec.ts +++ b/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.spec.ts @@ -1,10 +1,33 @@ import { v6 } from '@ovh-ux/manager-core-api'; import { describe, it, vi } from 'vitest'; -import { getLoadBalancers, TLoadBalancerResponse } from './load-balancer'; -import { mockLoadBalancers } from '@/mocks'; +import { + getLoadBalancer, + getLoadBalancers, + getLoadBalancerFlavor, + TLoadBalancerResponse, + deleteLoadBalancer, + getLoadBalancerListeners, + updateLoadBalancerName, + createListener, + CreateListenerProps, + editListener, + EditListenerProps, + createLoadBalancer, + TCreateLoadBalancerParam, +} from './load-balancer'; +import { + mockFlavor, + mockLoadBalancer, + mockLoadBalancerListeners, + mockLoadBalancers, +} from '@/mocks'; + +const projectId = 'test-project-id'; +const region = 'region1'; +const loadBalancerId = 'loadBalancerId'; +const flavorId = 'flavorId'; describe('getLoadBalancers', () => { - const projectId = 'test-project-id'; const mockResponse: TLoadBalancerResponse = { resources: mockLoadBalancers, errors: [], @@ -33,3 +56,333 @@ describe('getLoadBalancers', () => { await expect(getLoadBalancers(projectId)).rejects.toThrow(errorMessage); }); }); + +describe('getLoadBalancer', () => { + beforeEach(() => { + vi.mocked(v6.get).mockResolvedValue({ data: mockLoadBalancer }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should fetch load balancer for the given project ID, region and balancer id', async () => { + const result = await getLoadBalancer(projectId, region, loadBalancerId); + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/loadbalancer/${loadBalancerId}`, + ); + expect(result).toEqual(mockLoadBalancer); + }); + + it('should handle errors getLoadBalancer', async () => { + const errorMessage = 'Network Error'; + vi.mocked(v6.get).mockRejectedValue(new Error(errorMessage)); + + await expect( + getLoadBalancer(projectId, region, loadBalancerId), + ).rejects.toThrow(errorMessage); + }); +}); + +describe('getLoadBalancerFlavor', () => { + beforeEach(() => { + vi.mocked(v6.get).mockResolvedValue({ data: mockFlavor }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should fetch load balancer for getLoadBalancerFlavor success', async () => { + const result = await getLoadBalancerFlavor(projectId, region, flavorId); + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/flavor/${flavorId}`, + ); + expect(result).toEqual(mockFlavor); + }); + + it('should handle errors getLoadBalancerFlavor', async () => { + const errorMessage = 'Network Error'; + vi.mocked(v6.get).mockRejectedValue(new Error(errorMessage)); + + await expect( + getLoadBalancerFlavor(projectId, region, flavorId), + ).rejects.toThrow(errorMessage); + }); +}); + +describe('deleteLoadBalancer', () => { + beforeEach(() => { + vi.mocked(v6.delete).mockResolvedValue({ data: {} }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should fetch load balancer for deleteLoadBalancer success', async () => { + await deleteLoadBalancer(projectId, mockLoadBalancer); + expect(v6.delete).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${mockLoadBalancer.region}/loadbalancing/loadbalancer/${mockLoadBalancer.id}`, + ); + }); + + it('should handle errors deleteLoadBalancer', async () => { + const errorMessage = 'Network Error'; + vi.mocked(v6.delete).mockRejectedValue(new Error(errorMessage)); + + await expect( + deleteLoadBalancer(projectId, mockLoadBalancer), + ).rejects.toThrow(errorMessage); + }); +}); + +describe('getLoadBalancerListeners', () => { + beforeEach(() => { + vi.mocked(v6.get).mockResolvedValue({ data: mockLoadBalancerListeners }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should fetch load balancer for getLoadBalancerListeners success', async () => { + const result = await getLoadBalancerListeners( + projectId, + region, + loadBalancerId, + ); + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/listener?loadbalancerId=${loadBalancerId}`, + ); + expect(result).toEqual(mockLoadBalancerListeners); + }); + + it('should handle errors getLoadBalancerListeners', async () => { + const errorMessage = 'Network Error'; + vi.mocked(v6.get).mockRejectedValue(new Error(errorMessage)); + + await expect( + getLoadBalancerListeners(projectId, region, loadBalancerId), + ).rejects.toThrow(errorMessage); + }); +}); + +describe('updateLoadBalancerName', () => { + beforeEach(() => { + vi.mocked(v6.put).mockResolvedValue({ data: {} }); + }); + + it('should fetch load balancer for updateLoadBalancerName success', async () => { + await updateLoadBalancerName(projectId, mockLoadBalancer, 'newName'); + expect( + v6.put, + ).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${mockLoadBalancer.region}/loadbalancing/loadbalancer/${mockLoadBalancer.id}`, + { name: 'newName' }, + ); + }); + + it('should handle errors updateLoadBalancerName', async () => { + const errorMessage = 'Network Error'; + vi.mocked(v6.put).mockRejectedValue(new Error(errorMessage)); + + await expect( + updateLoadBalancerName(projectId, mockLoadBalancer, 'newName'), + ).rejects.toThrow(errorMessage); + }); + + describe('createListener', () => { + beforeEach(() => { + vi.mocked(v6.post).mockResolvedValue({ data: {} }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should create a new listener', async () => { + const listenerProps = { + projectId, + region, + loadBalancerId, + name: 'listenerName', + protocol: 'http', + port: 80, + defaultPoolId: 'defaultPoolId', + } as CreateListenerProps; + await createListener(listenerProps); + expect(v6.post).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/listener`, + { + loadbalancerId: loadBalancerId, + name: 'listenerName', + protocol: 'http', + port: 80, + defaultPoolId: 'defaultPoolId', + }, + ); + }); + + it('should handle errors createListener', async () => { + const errorMessage = 'Network Error'; + vi.mocked(v6.post).mockRejectedValue(new Error(errorMessage)); + + await expect( + createListener({ + projectId, + region, + loadBalancerId, + name: 'listenerName', + protocol: 'http', + port: 80, + }), + ).rejects.toThrow(errorMessage); + }); + }); + + describe('editListener', () => { + beforeEach(() => { + vi.mocked(v6.put).mockResolvedValue({ data: {} }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should edit an existing listener', async () => { + const listenerProps = { + projectId, + region, + name: 'newListenerName', + defaultPoolId: 'newDefaultPoolId', + listenerId: 'listenerId', + } as EditListenerProps; + await editListener(listenerProps); + expect(v6.put).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/listener/${listenerProps.listenerId}`, + { + name: 'newListenerName', + defaultPoolId: 'newDefaultPoolId', + }, + ); + }); + + it('should handle errors editListener', async () => { + const errorMessage = 'Network Error'; + vi.mocked(v6.put).mockRejectedValue(new Error(errorMessage)); + + await expect( + editListener({ + projectId, + region, + listenerId: 'listenerId', + name: 'newListenerName', + }), + ).rejects.toThrow(errorMessage); + }); + }); + describe('createLoadBalancer', () => { + beforeEach(() => { + vi.mocked(v6.post).mockResolvedValue({ data: {} }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should create a new load balancer', async () => { + const createLoadBalancerParams = { + projectId, + flavor: { id: 'flavorId', name: 'flavorName', region: 'region1' }, + region: { name: 'region1' }, + floatingIp: { type: 'create', id: 'floatingIpId' }, + privateNetwork: { id: 'privateNetworkId' }, + subnet: { id: 'subnetId' }, + gateways: [{ id: 'gatewayId' }], + listeners: [ + { + protocol: 'http', + port: 80, + instances: [ + { instance: { ipAddresses: [{ ip: '127.0.0.1' }] }, port: 80 }, + ], + healthMonitor: 'http', + }, + ], + name: 'loadBalancerName', + } as TCreateLoadBalancerParam; + + await createLoadBalancer(createLoadBalancerParams); + + expect(v6.post).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/region1/loadbalancing/loadbalancer`, + { + flavorId: 'flavorId', + network: { + private: { + network: { + id: 'privateNetworkId', + subnetId: 'subnetId', + }, + floatingIpCreate: { + description: + 'FIP created by OVHCloud Control Panel (Manager) for Load Balancer loadBalancerName', + }, + gateway: { + id: 'gatewayId', + }, + }, + }, + name: 'loadBalancerName', + listeners: [ + { + port: 80, + protocol: 'http', + pool: { + algorithm: 'roundRobin', + protocol: 'http', + healthMonitor: { + name: 'health-monitor-http', + monitorType: 'http', + maxRetries: 4, + delay: 5, + timeout: 4, + httpConfiguration: { + httpMethod: 'GET', + urlPath: '/', + }, + }, + members: [ + { + address: '127.0.0.1', + protocolPort: 80, + }, + ], + }, + }, + ], + }, + ); + }); + + it('should handle errors createLoadBalancer', async () => { + const errorMessage = 'Network Error'; + vi.mocked(v6.post).mockRejectedValue(new Error(errorMessage)); + + await expect( + createLoadBalancer({ + projectId, + flavor: { id: 'flavorId', name: 'flavorName', region: 'region1' }, + region: { name: 'region1' }, + floatingIp: { type: 'create', id: 'floatingIpId' }, + privateNetwork: { id: 'privateNetworkId' }, + subnet: { id: 'subnetId' }, + gateways: [{ id: 'gatewayId' }], + listeners: [], + name: 'loadBalancerName', + } as TCreateLoadBalancerParam), + ).rejects.toThrow(errorMessage); + }); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.ts b/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.ts index a55a15de594d..7868e9d35b47 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.ts +++ b/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.ts @@ -139,7 +139,7 @@ export const updateLoadBalancerName = async ( export type TProtocol = typeof PROTOCOLS[number]; -interface CreateListenerProps { +export interface CreateListenerProps { projectId: string; region: string; loadBalancerId: string; @@ -172,7 +172,7 @@ export const createListener = async ({ return data; }; -interface EditListenerProps { +export interface EditListenerProps { projectId: string; region: string; listenerId: string; diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/network.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/network.spec.ts new file mode 100644 index 000000000000..1c5958b8ead8 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/network.spec.ts @@ -0,0 +1,199 @@ +import { describe, it, expect, vi } from 'vitest'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { + getPrivateNetworkByRegion, + getPrivateNetworks, + getPrivateNetworkSubnets, + getRegionPrivateNetworks, + getSubnetByNetworkAndRegion, + TNetwork, + TPrivateNetwork, + TSubnet, +} from './network'; + +describe('API Data Network', () => { + describe('getPrivateNetworkByRegion', () => { + it('should return network data for a given project, region, and network ID', async () => { + const mockNetwork: TNetwork = { + id: 'network-id', + name: 'network-name', + visibility: 'private', + vlanId: 123, + }; + + vi.mocked(v6.get).mockResolvedValue({ data: mockNetwork }); + + const result = await getPrivateNetworkByRegion( + 'project-id', + 'region', + 'network-id', + ); + expect(result).toEqual(mockNetwork); + expect(v6.get).toHaveBeenCalledWith( + '/cloud/project/project-id/region/region/network/network-id', + ); + }); + + it('should throw an error if the API call fails', async () => { + vi.mocked(v6.get).mockRejectedValue(new Error('API Error')); + + await expect( + getPrivateNetworkByRegion('project-id', 'region', 'network-id'), + ).rejects.toThrow('API Error'); + }); + }); + + describe('getSubnetByNetworkAndRegion', () => { + it('should return subnet data for a given project, region, network ID, and subnet ID', async () => { + const mockSubnet = { + cidr: '192.168.1.0/24', + gatewayIp: '192.168.1.1', + id: 'subnet-id', + } as TSubnet; + + vi.mocked(v6.get).mockResolvedValue({ data: mockSubnet }); + + const result = await getSubnetByNetworkAndRegion( + 'project-id', + 'region', + 'network-id', + 'subnet-id', + ); + expect(result).toEqual(mockSubnet); + expect(v6.get).toHaveBeenCalledWith( + '/cloud/project/project-id/region/region/network/network-id/subnet/subnet-id', + ); + }); + + it('should throw an error if the API call fails', async () => { + vi.mocked(v6.get).mockRejectedValue(new Error('API Error')); + + await expect( + getSubnetByNetworkAndRegion( + 'project-id', + 'region', + 'network-id', + 'subnet-id', + ), + ).rejects.toThrow('API Error'); + }); + }); + describe('getPrivateNetworks', () => { + it('should return a list of private networks for a given project', async () => { + const mockNetworks: TPrivateNetwork[] = [ + { + id: 'network-id-1', + name: 'network-name-1', + status: 'ACTIVE', + type: 'private', + vlanId: 123, + visibility: 'private', + regions: [ + { + openstackId: 'openstack-id-1', + region: 'region-1', + status: 'ACTIVE', + }, + ], + }, + ]; + + vi.mocked(v6.get).mockResolvedValue({ data: mockNetworks }); + + const result = await getPrivateNetworks('project-id'); + expect(result).toEqual(mockNetworks); + expect(v6.get).toHaveBeenCalledWith( + '/cloud/project/project-id/network/private', + ); + }); + + it('should throw an error if the API call fails', async () => { + vi.mocked(v6.get).mockRejectedValue(new Error('API Error')); + + await expect(getPrivateNetworks('project-id')).rejects.toThrow( + 'API Error', + ); + }); + }); + + describe('getPrivateNetworkSubnets', () => { + it('should return a list of subnets for a given project, region, and network ID', async () => { + const mockSubnets: TSubnet[] = [ + { + id: 'subnet-id-1', + name: 'subnet-name-1', + cidr: '192.168.1.0/24', + ipVersion: 4, + dhcpEnabled: true, + gatewayIp: '192.168.1.1', + allocationPools: [ + { + start: '192.168.1.2', + end: '192.168.1.254', + }, + ], + hostRoutes: [], + dnsNameServers: ['8.8.8.8'], + }, + ]; + + vi.mocked(v6.get).mockResolvedValue({ data: mockSubnets }); + + const result = await getPrivateNetworkSubnets( + 'project-id', + 'region', + 'network-id', + ); + expect(result).toEqual(mockSubnets); + expect(v6.get).toHaveBeenCalledWith( + '/cloud/project/project-id/region/region/network/network-id/subnet', + ); + }); + + it('should throw an error if the API call fails', async () => { + vi.mocked(v6.get).mockRejectedValue(new Error('API Error')); + + await expect( + getPrivateNetworkSubnets('project-id', 'region', 'network-id'), + ).rejects.toThrow('API Error'); + }); + }); + + describe('getRegionPrivateNetworks', () => { + it('should return a list of private networks for a given project and region', async () => { + const mockNetworks: TPrivateNetwork[] = [ + { + id: 'network-id-1', + name: 'network-name-1', + status: 'ACTIVE', + type: 'private', + vlanId: 123, + visibility: 'private', + regions: [ + { + openstackId: 'openstack-id-1', + region: 'region-1', + status: 'ACTIVE', + }, + ], + }, + ]; + + vi.mocked(v6.get).mockResolvedValue({ data: mockNetworks }); + + const result = await getRegionPrivateNetworks('project-id', 'region'); + expect(result).toEqual(mockNetworks); + expect(v6.get).toHaveBeenCalledWith( + '/cloud/project/project-id/region/region/network', + ); + }); + + it('should throw an error if the API call fails', async () => { + vi.mocked(v6.get).mockRejectedValue(new Error('API Error')); + + await expect( + getRegionPrivateNetworks('project-id', 'region'), + ).rejects.toThrow('API Error'); + }); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/plans.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/plans.spec.ts new file mode 100644 index 000000000000..be3c941ba7c2 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/plans.spec.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, vi } from 'vitest'; +import { v6 } from '@ovh-ux/manager-core-api'; +import { getPlans, TPlanResponse } from './plans'; + +describe('getPlans', () => { + it('should fetch plans successfully', async () => { + const mockResponse: TPlanResponse = { + plans: [ + { + code: 'plan1', + regions: [ + { + continentCode: 'EU', + datacenter: 'dc1', + enabled: true, + name: 'region1', + type: 'public', + }, + ], + }, + ], + }; + + vi.mocked(v6.get).mockResolvedValueOnce({ data: mockResponse }); + + const projectId = 'test-project'; + const ovhSubsidiary = 'FR'; + const result = await getPlans(projectId, ovhSubsidiary); + + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/capabilities/productAvailability?addonFamily=octavia-loadbalancer&ovhSubsidiary=${ovhSubsidiary}`, + ); + expect(result).toEqual(mockResponse); + }); + + it('should handle errors', async () => { + vi.mocked(v6.get).mockRejectedValueOnce(new Error('API Error')); + + const projectId = 'test-project'; + const ovhSubsidiary = 'FR'; + + await expect(getPlans(projectId, ovhSubsidiary)).rejects.toThrow( + 'API Error', + ); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/pool-member.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/pool-member.spec.ts new file mode 100644 index 000000000000..a8b4334164d4 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/pool-member.spec.ts @@ -0,0 +1,102 @@ +import { v6 } from '@ovh-ux/manager-core-api'; +import { + getPoolMembers, + deletePoolMember, + getPoolMember, + updatePoolMemberName, + createPoolMembers, + TPoolMember, +} from './pool-member'; +import { + LoadBalancerProvisioningStatusEnum, + LoadBalancerOperatingStatusEnum, +} from './load-balancer'; + +describe('Pool Member API', () => { + const projectId = 'test-project'; + const region = 'test-region'; + const poolId = 'test-pool'; + const memberId = 'test-member'; + const mockPoolMember: TPoolMember = { + id: 'test-id', + name: 'test-name', + operatingStatus: LoadBalancerOperatingStatusEnum.ONLINE, + provisioningStatus: LoadBalancerProvisioningStatusEnum.ACTIVE, + address: '127.0.0.1', + protocolPort: 80, + weight: 1, + }; + + it('should get pool members', async () => { + const mockData = [mockPoolMember]; + (v6.get as jest.Mock).mockResolvedValue({ data: mockData }); + + const result = await getPoolMembers(projectId, region, poolId); + + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}/member`, + ); + expect(result).toEqual(mockData); + }); + + it('should delete a pool member', async () => { + const mockData = { success: true }; + (v6.delete as jest.Mock).mockResolvedValue({ data: mockData }); + + const result = await deletePoolMember(projectId, region, poolId, memberId); + + expect(v6.delete).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}/member/${memberId}`, + ); + expect(result).toEqual(mockData); + }); + + it('should get a pool member', async () => { + (v6.get as jest.Mock).mockResolvedValue({ data: mockPoolMember }); + + const result = await getPoolMember(projectId, region, poolId, memberId); + + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}/member/${memberId}`, + ); + expect(result).toEqual(mockPoolMember); + }); + + it('should update a pool member name', async () => { + const newName = 'new-name'; + const mockData = { success: true }; + (v6.put as jest.Mock).mockResolvedValue({ data: mockData }); + + const result = await updatePoolMemberName( + projectId, + region, + poolId, + memberId, + newName, + ); + + expect( + v6.put, + ).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}/member/${memberId}`, + { name: newName }, + ); + expect(result).toEqual(mockData); + }); + + it('should create pool members', async () => { + const mockData = { success: true }; + const members = [mockPoolMember]; + (v6.post as jest.Mock).mockResolvedValue({ data: mockData }); + + const result = await createPoolMembers(projectId, region, poolId, members); + + expect( + v6.post, + ).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}/member`, + { members }, + ); + expect(result).toEqual(mockData); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/pool.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/data/pool.spec.ts new file mode 100644 index 000000000000..e6346426f39b --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/data/pool.spec.ts @@ -0,0 +1,202 @@ +import { v6 } from '@ovh-ux/manager-core-api'; +import { vi } from 'vitest'; +import { + getLoadBalancerPools, + deletePool, + createPool, + updatePool, + getPool, + TLoadBalancerPool, + TCreatePoolParam, + TUpdatePoolParam, +} from './pool'; +import { + LoadBalancerOperatingStatusEnum, + LoadBalancerProvisioningStatusEnum, +} from './load-balancer'; + +describe('Load Balancer Pool API', () => { + const projectId = 'test-project'; + const region = 'test-region'; + const loadBalancerId = 'test-loadbalancer'; + const poolId = 'test-pool'; + const poolData: TLoadBalancerPool = { + id: 'test-pool', + name: 'test-pool', + protocol: 'http', + algorithm: 'ROUND_ROBIN', + operatingStatus: LoadBalancerOperatingStatusEnum.ONLINE, + provisioningStatus: LoadBalancerProvisioningStatusEnum.ACTIVE, + sessionPersistence: { + type: 'HTTP_COOKIE', + cookieName: 'test-cookie', + }, + loadbalancerId: 'test-loadbalancer', + listenerId: 'test-listener', + }; + + it('should fetch load balancer pools', async () => { + vi.mocked(v6.get).mockResolvedValue({ data: [poolData] }); + + const result = await getLoadBalancerPools( + projectId, + region, + loadBalancerId, + ); + + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool?loadbalancerId=${loadBalancerId}`, + ); + expect(result).toEqual([poolData]); + }); + + it('should delete a pool', async () => { + vi.mocked(v6.delete).mockResolvedValue({ data: {} }); + + const result = await deletePool(projectId, region, poolId); + + expect(v6.delete).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}`, + ); + expect(result).toEqual({}); + }); + + it('should create a pool', async () => { + const createPoolParam: TCreatePoolParam = { + projectId, + region, + loadbalancerId: loadBalancerId, + name: 'test-pool', + algorithm: 'ROUND_ROBIN', + protocol: 'HTTP', + permanentSession: { + isEnabled: true, + type: 'HTTP_COOKIE', + cookieName: 'test-cookie', + }, + }; + + vi.mocked(v6.post).mockResolvedValue({ data: poolData }); + + const result = await createPool(createPoolParam); + + expect(v6.post).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool`, + { + loadbalancerId: loadBalancerId, + name: 'test-pool', + algorithm: 'ROUND_ROBIN', + protocol: 'HTTP', + sessionPersistence: { + type: 'HTTP_COOKIE', + cookieName: 'test-cookie', + }, + }, + ); + expect(result).toEqual(poolData); + }); + + it('should create a pool without sessionPersistence', async () => { + const createPoolParam: TCreatePoolParam = { + projectId, + region, + loadbalancerId: loadBalancerId, + name: 'test-pool', + algorithm: 'ROUND_ROBIN', + protocol: 'HTTP', + permanentSession: { + isEnabled: false, + type: 'HTTP_COOKIE', + cookieName: 'test-cookie', + }, + }; + + vi.mocked(v6.post).mockResolvedValue({ data: poolData }); + + const result = await createPool(createPoolParam); + + expect(v6.post).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool`, + { + loadbalancerId: loadBalancerId, + name: 'test-pool', + algorithm: 'ROUND_ROBIN', + protocol: 'HTTP', + sessionPersistence: null, + }, + ); + expect(result).toEqual(poolData); + }); + + it('should update a pool', async () => { + const updatePoolParam: TUpdatePoolParam = { + projectId, + region, + poolId, + name: 'updated-pool', + algorithm: 'LEAST_CONNECTIONS', + permanentSession: { + isEnabled: true, + type: 'SOURCE_IP', + cookieName: 'updated-cookie', + }, + }; + + vi.mocked(v6.put).mockResolvedValue({ data: poolData }); + + const result = await updatePool(updatePoolParam); + + expect(v6.put).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}`, + { + name: 'updated-pool', + algorithm: 'LEAST_CONNECTIONS', + sessionPersistence: { + type: 'SOURCE_IP', + cookieName: 'updated-cookie', + }, + }, + ); + expect(result).toEqual(poolData); + }); + + it('should update a pool without session', async () => { + const updatePoolParam: TUpdatePoolParam = { + projectId, + region, + poolId, + name: 'updated-pool', + algorithm: 'LEAST_CONNECTIONS', + permanentSession: { + isEnabled: false, + type: 'SOURCE_IP', + cookieName: 'updated-cookie', + }, + }; + + vi.mocked(v6.put).mockResolvedValue({ data: poolData }); + + const result = await updatePool(updatePoolParam); + + expect(v6.put).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}`, + { + name: 'updated-pool', + algorithm: 'LEAST_CONNECTIONS', + sessionPersistence: { type: 'disabled' }, + }, + ); + expect(result).toEqual(poolData); + }); + + it('should fetch a single pool', async () => { + vi.mocked(v6.get).mockResolvedValue({ data: poolData }); + + const result = await getPool(projectId, region, poolId); + + expect(v6.get).toHaveBeenCalledWith( + `/cloud/project/${projectId}/region/${region}/loadbalancing/pool/${poolId}`, + ); + expect(result).toEqual(poolData); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/pool.ts b/packages/manager/apps/pci-load-balancer/src/api/data/pool.ts index a2f45accb7dd..cab98f34ac23 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/data/pool.ts +++ b/packages/manager/apps/pci-load-balancer/src/api/data/pool.ts @@ -17,6 +17,7 @@ export type TLoadBalancerPool = { cookieName: string; }; loadbalancerId: string; + search?: string; listenerId: string; }; diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useAddons.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/hook/useAddons.spec.ts new file mode 100644 index 000000000000..11513869ce68 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useAddons.spec.ts @@ -0,0 +1,95 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { TCatalog, useCatalog } from '@ovh-ux/manager-pci-common'; +import { vi } from 'vitest'; +import { UseQueryResult } from '@tanstack/react-query'; +import { useGetAddons } from './useAddons'; +import { wrapper } from '@/wrapperRenders'; + +describe('useGetAddons', () => { + it('should return empty data when catalog is undefined', async () => { + vi.mocked(useCatalog).mockReturnValue({ + data: undefined, + isPending: false, + } as UseQueryResult); + + const { result } = renderHook(() => useGetAddons(), { wrapper }); + await waitFor(() => expect(result.current.isPending).toBe(false)); + expect(result.current.data).toEqual([]); + }); + + it('should return sorted addons when catalog has addons', () => { + const mockCatalog = { + addons: [ + { + planCode: 'octavia-loadbalancer.loadbalancer-s.hour.consumption', + pricings: [{ price: 10 }], + blobs: { technical: { name: 'small-addon' } }, + }, + { + planCode: 'octavia-loadbalancer.loadbalancer-m.hour.consumption', + pricings: [{ price: 20 }], + blobs: { technical: { name: 'medium-addon' } }, + }, + { + planCode: 'octavia-loadbalancer.loadbalancer-l.hour.consumption', + pricings: [{ price: 30 }], + blobs: { technical: { name: 'large-addon' } }, + }, + ], + }; + vi.mocked(useCatalog).mockReturnValue({ + data: mockCatalog, + isPending: false, + } as UseQueryResult); + + const { result } = renderHook(() => useGetAddons()); + + expect(result.current.data).toEqual([ + { code: 's', price: 10, label: 'S', technicalName: 'small-addon' }, + { code: 'm', price: 20, label: 'M', technicalName: 'medium-addon' }, + { code: 'l', price: 30, label: 'L', technicalName: 'large-addon' }, + ]); + expect(result.current.isPending).toBe(false); + }); + + it('should return isPending as true when catalog is loading', () => { + vi.mocked(useCatalog).mockReturnValue({ + data: undefined, + isPending: true, + } as UseQueryResult); + + const { result } = renderHook(() => useGetAddons()); + + expect(result.current.data).toEqual([]); + expect(result.current.isPending).toBe(true); + }); + + it('should filter out addons that do not match the regex', async () => { + const mockCatalog = { + addons: [ + { + planCode: 'octavia-loadbalancer.loadbalancer-s.hour.consumption', + pricings: [{ price: 10 }], + blobs: { technical: { name: 'small-addon' } }, + }, + { + planCode: 'some-other-plan-code', + pricings: [{ price: 15 }], + blobs: { technical: { name: 'other-addon' } }, + }, + ], + }; + vi.mocked(useCatalog).mockReturnValue({ + data: mockCatalog, + isPending: false, + } as UseQueryResult); + + const { result } = renderHook(() => useGetAddons()); + + await waitFor(() => expect(result.current.isPending).toBe(false)); + expect(result.current.data).toEqual([ + { code: 's', price: 10, label: 'S', technicalName: 'small-addon' }, + ]); + expect(result.current.isPending).toBe(false); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useFlavors.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/hook/useFlavors.spec.ts new file mode 100644 index 000000000000..30bba3726dea --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useFlavors.spec.ts @@ -0,0 +1,34 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { useGetFlavor } from './useFlavors'; +import { getFlavor } from '@/api/data/flavors'; +import { TAddon } from '@/pages/create/store'; +import { wrapper } from '@/wrapperRenders'; +import { TFlavor } from '../data/load-balancer'; + +vi.mock('@/api/data/flavors'); + +describe('useGetFlavor', () => { + const projectId = 'test-project'; + const regionName = 'test-region'; + const addon = { code: 'test-addon' } as TAddon; + + it('should fetch flavor data successfully', async () => { + const mockFlavorData = { + name: 'flavor-data', + region: 'region1', + id: 'id1', + } as TFlavor; + vi.mocked(getFlavor).mockResolvedValueOnce(mockFlavorData); + + const { result } = renderHook( + () => useGetFlavor(projectId, regionName, addon), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockFlavorData); + expect(getFlavor).toHaveBeenCalledWith(projectId, regionName, addon); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useFloatingIps.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/hook/useFloatingIps.spec.ts new file mode 100644 index 000000000000..79d98473d55a --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useFloatingIps.spec.ts @@ -0,0 +1,25 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { useGetFloatingIps } from './useFloatingIps'; +import { getFloatingIps, TFloatingIp } from '../data/floating-ips'; +import { wrapper } from '@/wrapperRenders'; + +vi.mock('../data/floating-ips'); + +describe('useGetFloatingIps', () => { + it('should fetch floating IPs successfully', async () => { + const mockData = [{ id: '1', ip: '192.168.0.1' }] as TFloatingIp[]; + vi.mocked(getFloatingIps).mockResolvedValueOnce(mockData); + + const { result } = renderHook( + () => useGetFloatingIps('project1', 'region1'), + { + wrapper, + }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useGateways.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/hook/useGateways.spec.ts new file mode 100644 index 000000000000..cea60395a497 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useGateways.spec.ts @@ -0,0 +1,34 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { useGetSubnetGateways } from './useGateways'; +import { getSubnetGateways, TSubnetGateway } from '@/api/data/gateways'; +import { wrapper } from '@/wrapperRenders'; + +vi.mock('@/api/data/gateways'); +describe('useGetSubnetGateways', () => { + const projectId = 'test-project'; + const regionName = 'test-region'; + const subnetId = 'test-subnet'; + + it('should fetch subnet gateways successfully', async () => { + const mockData = [ + { id: 'gateway-1' }, + { id: 'gateway-2' }, + ] as TSubnetGateway[]; + vi.mocked(getSubnetGateways).mockResolvedValueOnce(mockData); + + const { result } = renderHook( + () => useGetSubnetGateways(projectId, regionName, subnetId), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + expect(getSubnetGateways).toHaveBeenCalledWith( + projectId, + regionName, + subnetId, + ); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useHealthMonitor.spec.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/useHealthMonitor.spec.tsx new file mode 100644 index 000000000000..994b82973580 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useHealthMonitor.spec.tsx @@ -0,0 +1,159 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { + useGetHealthMonitor, + useDeleteHealthMonitor, + useCreateHealthMonitor, + useEditHealthMonitor, + useRenameHealthMonitor, +} from './useHealthMonitor'; +import { wrapper } from '@/wrapperRenders'; +import { + createHealthMonitor, + deleteHealthMonitor, + editHealthMonitor, + getHealthMonitor, + renameHealthMonitor, + THealthMonitor, + THealthMonitorFormState, +} from '../data/health-monitor'; + +vi.mock('@/api/data/health-monitor'); + +describe('useHealthMonitor hooks', () => { + const projectId = 'test-project'; + const region = 'test-region'; + const poolId = 'test-pool'; + const healthMonitorId = 'test-health-monitor'; + const model = { name: 'test-name' } as THealthMonitorFormState; + + it('should fetch health monitor', async () => { + vi.mocked(getHealthMonitor).mockResolvedValue([ + { id: healthMonitorId } as THealthMonitor, + ]); + + const { result } = renderHook( + () => useGetHealthMonitor({ projectId, region, poolId }), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual({ id: healthMonitorId }); + expect(getHealthMonitor).toHaveBeenCalledWith(projectId, region, poolId); + }); + + it('should delete health monitor', async () => { + vi.mocked(deleteHealthMonitor).mockResolvedValue([ + { id: healthMonitorId } as THealthMonitor, + ]); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => useDeleteHealthMonitor({ projectId, region, onError, onSuccess }), + { wrapper }, + ); + + await act(async () => { + result.current.deleteHealthMonitor(healthMonitorId); + }); + + expect(deleteHealthMonitor).toHaveBeenCalledWith( + projectId, + region, + healthMonitorId, + ); + expect(onSuccess).toHaveBeenCalled(); + }); + + it('should create health monitor', async () => { + vi.mocked(createHealthMonitor).mockResolvedValue({} as never); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useCreateHealthMonitor({ + projectId, + region, + poolId, + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.createHealthMonitor(model); + }); + + expect(createHealthMonitor).toHaveBeenCalledWith( + projectId, + region, + poolId, + model, + ); + expect(onSuccess).toHaveBeenCalled(); + }); + + it('should edit health monitor', async () => { + vi.mocked(editHealthMonitor).mockResolvedValue({} as never); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useEditHealthMonitor({ + projectId, + region, + healthMonitorId, + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.editHealthMonitor(model); + }); + + expect(editHealthMonitor).toHaveBeenCalledWith( + projectId, + region, + healthMonitorId, + model, + ); + expect(onSuccess).toHaveBeenCalled(); + }); + + it('should rename health monitor', async () => { + vi.mocked(renameHealthMonitor).mockResolvedValue({} as never); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useRenameHealthMonitor({ + projectId, + region, + healthMonitorId, + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.renameHealthMonitor('new-name'); + }); + + expect(renameHealthMonitor).toHaveBeenCalledWith( + projectId, + region, + healthMonitorId, + 'new-name', + ); + expect(onSuccess).toHaveBeenCalled(); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Policy.spec.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Policy.spec.tsx new file mode 100644 index 000000000000..5d6f374fd4e2 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Policy.spec.tsx @@ -0,0 +1,316 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { UseQueryResult } from '@tanstack/react-query'; +import { applyFilters } from '@ovh-ux/manager-core-api'; +import { ColumnSort, PaginationState } from '@tanstack/react-table'; +import { + useGetAllL7Policies, + useL7Policies, + useGetPolicy, + useDeletePolicy, + useCreatePolicy, + getAttribute, + useUpdatePolicy, + setSearchPolicy, +} from './useL7Policy'; +import { + getL7Policies, + getPolicy, + deletePolicy, + createPolicy, + updatePolicy, + TL7Policy, +} from '@/api/data/l7Policies'; +import { wrapper } from '@/wrapperRenders'; +import * as useL7PolicyModule from './useL7Policy'; +import { sortResults, paginateResults } from '@/helpers'; +import { ACTIONS } from '@/constants'; +import { + LoadBalancerOperatingStatusEnum, + LoadBalancerProvisioningStatusEnum, +} from '../data/load-balancer'; + +vi.mock('@/api/data/l7Policies'); + +describe('useL7Policy hooks', () => { + const projectId = 'test-project'; + const listenerId = 'test-listener'; + const region = 'test-region'; + const policyId = 'test-policy'; + const pagination: PaginationState = { pageIndex: 0, pageSize: 10 }; + const sorting: ColumnSort = { id: 'position', desc: false }; + const filters = []; + + it('should fetch all L7 policies', async () => { + vi.mocked(getL7Policies).mockResolvedValue([]); + const { result } = renderHook( + () => useGetAllL7Policies(projectId, listenerId, region), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual([]); + expect(getL7Policies).toHaveBeenCalledWith(projectId, listenerId, region); + }); + + it('should fetch a single L7 policy', async () => { + const policy = { id: policyId } as TL7Policy; + vi.mocked(getPolicy).mockResolvedValue(policy); + const { result } = renderHook( + () => useGetPolicy(projectId, policyId, region), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(policy); + expect(getPolicy).toHaveBeenCalledWith(projectId, region, policyId); + }); + + it('should delete a policy', async () => { + const onError = vi.fn(); + const onSuccess = vi.fn(); + vi.mocked(deletePolicy).mockResolvedValue({}); + const { result } = renderHook( + () => + useDeletePolicy({ + projectId, + policyId, + region, + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.deletePolicy(); + }); + + expect(deletePolicy).toHaveBeenCalledWith(projectId, region, policyId); + expect(onSuccess).toHaveBeenCalled(); + }); + + it('should create a policy', async () => { + const newPolicy = { id: 'new-policy' } as TL7Policy; + const onError = vi.fn(); + const onSuccess = vi.fn(); + (createPolicy as jest.Mock).mockResolvedValue(newPolicy); + const { result } = renderHook( + () => + useCreatePolicy({ + projectId, + listenerId, + region, + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.createPolicy(newPolicy); + }); + + expect(createPolicy).toHaveBeenCalledWith( + projectId, + region, + listenerId, + newPolicy, + ); + expect(onSuccess).toHaveBeenCalledWith(newPolicy); + }); + + it('should update a policy', async () => { + const updatedPolicy = { id: policyId } as TL7Policy; + const onError = vi.fn(); + const onSuccess = vi.fn(); + vi.mocked(updatePolicy).mockResolvedValue(updatedPolicy); + const { result } = renderHook( + () => + useUpdatePolicy({ + projectId, + region, + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.updatePolicy(updatedPolicy); + }); + + expect(updatePolicy).toHaveBeenCalledWith(projectId, region, updatedPolicy); + expect(onSuccess).toHaveBeenCalledWith(updatedPolicy); + }); + + it('should handle pagination and sorting', async () => { + const mockL7Policies = [ + { + id: 'id', + position: 1, + name: 'policy', + }, + ] as TL7Policy[]; + vi.mocked(applyFilters).mockReturnValue(mockL7Policies); + vi.mocked(sortResults).mockReturnValue(mockL7Policies); + vi.mocked(paginateResults).mockReturnValue({ + rows: mockL7Policies, + pageCount: 1, + totalRows: 1, + }); + vi.spyOn(useL7PolicyModule, 'useGetAllL7Policies').mockResolvedValue({ + data: [{ position: 1 }], + } as UseQueryResult); + const { result } = renderHook( + () => + useL7Policies( + projectId, + listenerId, + region, + pagination, + sorting, + filters, + ), + { wrapper }, + ); + await waitFor(() => expect(result.current.isLoading).toBe(false)); + expect(result.current.paginatedL7Policies).toEqual({ + rows: mockL7Policies, + totalRows: 1, + pageCount: 1, + }); + }); + + it('should handle pagination and default sorting', async () => { + const mockL7Policies = [ + { + id: 'id', + position: 1, + name: 'policy', + }, + ] as TL7Policy[]; + vi.mocked(applyFilters).mockReturnValue(mockL7Policies); + vi.mocked(sortResults).mockReturnValue(mockL7Policies); + vi.mocked(paginateResults).mockReturnValue({ + rows: mockL7Policies, + pageCount: 1, + totalRows: 1, + }); + vi.spyOn(useL7PolicyModule, 'useGetAllL7Policies').mockResolvedValue({ + data: [{ position: 1 }], + } as UseQueryResult); + const { result } = renderHook( + () => + useL7Policies(projectId, listenerId, region, pagination, null, filters), + { wrapper }, + ); + await waitFor(() => expect(result.current.isLoading).toBe(false)); + expect(result.current.paginatedL7Policies).toEqual({ + rows: mockL7Policies, + totalRows: 1, + pageCount: 1, + }); + }); +}); + +describe('getAttribute', () => { + it('should return redirectUrl for REDIRECT_TO_URL action', () => { + const policy = { + action: ACTIONS.REDIRECT_TO_URL, + redirectUrl: 'http://example.com', + } as TL7Policy; + const result = getAttribute(policy); + expect(result).toBe('http://example.com'); + }); + + it('should return redirectPrefix for REDIRECT_PREFIX action', () => { + const policy = { + action: ACTIONS.REDIRECT_PREFIX, + redirectPrefix: '/prefix', + } as TL7Policy; + const result = getAttribute(policy); + expect(result).toBe('/prefix'); + }); + + it('should return redirectPoolId for REDIRECT_TO_POOL action', () => { + const policy = { + action: ACTIONS.REDIRECT_TO_POOL, + redirectPoolId: 'pool-id', + } as TL7Policy; + const result = getAttribute(policy); + expect(result).toBe('pool-id'); + }); + + it('should return "-" for unknown action', () => { + const policy = { action: 'UNKNOWN_ACTION' } as TL7Policy; + const result = getAttribute(policy); + expect(result).toBe('-'); + }); +}); +describe('setSearchPolicy', () => { + it('should set search attribute correctly for REDIRECT_TO_URL action', () => { + const policies = [ + { + id: '1', + action: ACTIONS.REDIRECT_TO_URL, + redirectUrl: 'http://example.com', + position: 1, + name: 'policy1', + redirectHttpCode: 301, + redirectPrefix: '/prefix', + redirectPoolId: 'pool-id', + provisioningStatus: LoadBalancerProvisioningStatusEnum.ACTIVE, + operatingStatus: LoadBalancerOperatingStatusEnum.ONLINE, + }, + ] as TL7Policy[]; + const result = setSearchPolicy(policies); + expect(result[0].search).toContain( + '1 policy1 Redirect to URL http://example.com 301 active online http://example.com /prefix pool-id', + ); + }); + + it('should set search attribute correctly for REDIRECT_PREFIX action', () => { + const policies = [ + { + id: '2', + action: ACTIONS.REDIRECT_PREFIX, + redirectPrefix: '/prefix', + position: 2, + name: 'policy2', + redirectHttpCode: 302, + redirectUrl: 'http://example.com', + redirectPoolId: 'pool-id', + provisioningStatus: LoadBalancerProvisioningStatusEnum.UPDATING, + operatingStatus: LoadBalancerOperatingStatusEnum.OFFLINE, + }, + ] as TL7Policy[]; + const result = setSearchPolicy(policies); + expect(result[0].search).toContain( + '2 policy2 Redirect Prefix /prefix 302 updating offline http://example.com /prefix pool-id', + ); + }); + + it('should set search attribute correctly for REDIRECT_TO_POOL action', () => { + const policies = [ + { + id: '3', + action: ACTIONS.REDIRECT_TO_POOL, + redirectPoolId: 'pool-id', + position: 3, + name: 'policy3', + redirectHttpCode: 303, + redirectPrefix: '/prefix', + redirectUrl: 'http://example.com', + provisioningStatus: LoadBalancerProvisioningStatusEnum.ERROR, + operatingStatus: LoadBalancerOperatingStatusEnum.DEGRADED, + }, + ] as TL7Policy[]; + const result = setSearchPolicy(policies); + expect(result[0].search).toContain( + '3 policy3 Redirect to Pool pool-id 303 error degraded http://example.com /prefix pool-id', + ); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Policy.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Policy.tsx index 431f6ba290e1..3b66d3232387 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Policy.tsx +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Policy.tsx @@ -14,7 +14,7 @@ import { paginateResults, sortResults } from '@/helpers'; import { ACTION_LABELS, ACTIONS } from '@/constants'; import queryClient from '@/queryClient'; -const getAttribute = (policy: TL7Policy) => { +export const getAttribute = (policy: TL7Policy) => { switch (policy.action) { case ACTIONS.REDIRECT_TO_URL: return policy.redirectUrl; @@ -27,6 +27,18 @@ const getAttribute = (policy: TL7Policy) => { } }; +export const setSearchPolicy = (l7Policies: TL7Policy[]): TL7Policy[] => + l7Policies.map((l7Policy) => { + const action = ACTION_LABELS[l7Policy.action]; + const attribute = getAttribute(l7Policy); + return { + ...l7Policy, + attribute, + action, + search: `${l7Policy.position} ${l7Policy.name} ${action} ${attribute} ${l7Policy.redirectHttpCode} ${l7Policy.provisioningStatus} ${l7Policy.operatingStatus} ${l7Policy.redirectUrl} ${l7Policy.redirectPrefix} ${l7Policy.redirectPoolId}`, + }; + }); + export const useGetAllL7Policies = ( projectId: string, listenerId: string, @@ -35,17 +47,7 @@ export const useGetAllL7Policies = ( useQuery({ queryKey: ['l7Policies', projectId, 'listeners', listenerId, region], queryFn: () => getL7Policies(projectId, listenerId, region), - select: (l7Policies) => - l7Policies.map((l7Policy) => { - const action = ACTION_LABELS[l7Policy.action]; - const attribute = getAttribute(l7Policy); - return { - ...l7Policy, - attribute, - action, - search: `${l7Policy.position} ${l7Policy.name} ${action} ${attribute} ${l7Policy.redirectHttpCode} ${l7Policy.provisioningStatus} ${l7Policy.operatingStatus} ${l7Policy.redirectUrl} ${l7Policy.redirectPrefix} ${l7Policy.redirectPoolId}`, - }; - }), + select: (l7Policies) => setSearchPolicy(l7Policies), }); export const useL7Policies = ( diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Rule.spec.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Rule.spec.tsx new file mode 100644 index 000000000000..95f5e229deae --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Rule.spec.tsx @@ -0,0 +1,214 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { applyFilters } from '@ovh-ux/manager-core-api'; +import { + useGetAllL7Rules, + useL7Rules, + useDeleteL7Rule, + useCreateL7Rule, + useUpdateL7Rule, + useGetL7Rule, +} from './useL7Rule'; +import { + createL7Rule, + deleteL7Rule, + getL7Rule, + getL7Rules, + TL7Rule, + updateL7Rule, +} from '@/api/data/l7Rules'; +import { wrapper } from '@/wrapperRenders'; +import { + LoadBalancerOperatingStatusEnum, + LoadBalancerProvisioningStatusEnum, +} from '../data/load-balancer'; +import { sortResults, paginateResults } from '@/helpers'; + +vi.mock('@/api/data/l7Rules', () => ({ + createL7Rule: vi.fn(), + deleteL7Rule: vi.fn(), + getL7Rule: vi.fn(), + getL7Rules: vi.fn(), + updateL7Rule: vi.fn(), +})); + +describe('useGetAllL7Rules', () => { + it('fetches and returns all L7 rules', async () => { + const mockRules = [ + { + id: '1', + key: 'key1', + value: 'value1', + invert: false, + ruleType: 'type', + compareType: 'compareType', + provisioningStatus: LoadBalancerProvisioningStatusEnum.ACTIVE, + operatingStatus: LoadBalancerOperatingStatusEnum.ONLINE, + search: 'key1 value1 false type compareType active online', + }, + ] as TL7Rule[]; + vi.mocked(getL7Rules).mockResolvedValueOnce(mockRules); + + const { result } = renderHook( + () => useGetAllL7Rules('project1', 'policy1', 'region1'), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockRules); + }); +}); + +describe('useL7Rules', () => { + it('returns paginated and sorted L7 rules', async () => { + const mockRules = [{ id: '1', key: 'key1', value: 'value1' }] as TL7Rule[]; + vi.mocked(getL7Rules).mockResolvedValueOnce(mockRules); + vi.mocked(applyFilters).mockReturnValue(mockRules); + vi.mocked(sortResults).mockReturnValue(mockRules); + vi.mocked(paginateResults).mockReturnValue({ + rows: mockRules, + pageCount: 1, + totalRows: 1, + }); + + const { result } = renderHook( + () => + useL7Rules( + 'project1', + 'policy1', + 'region1', + { pageIndex: 0, pageSize: 10 }, + { id: 'asc', desc: false }, + [], + ), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isLoading).toBe(false)); + + expect(result.current.paginatedL7Rules).toEqual({ + rows: mockRules, + pageCount: 1, + totalRows: 1, + }); + }); +}); + +describe('useDeleteL7Rule', () => { + it('deletes an L7 rule', async () => { + vi.mocked(deleteL7Rule).mockResolvedValueOnce({}); + + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useDeleteL7Rule({ + projectId: 'project1', + policyId: 'policy1', + ruleId: 'rule1', + region: 'region1', + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.deleteL7Rule(); + }); + + expect(deleteL7Rule).toHaveBeenCalledWith( + 'project1', + 'region1', + 'policy1', + 'rule1', + ); + expect(onSuccess).toHaveBeenCalled(); + }); +}); + +describe('useCreateL7Rule', () => { + it('creates a new L7 rule', async () => { + const newRule = { id: '1', key: 'key1', value: 'value1' } as TL7Rule; + vi.mocked(createL7Rule).mockResolvedValueOnce(newRule); + + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useCreateL7Rule({ + projectId: 'project1', + policyId: 'policy1', + region: 'region1', + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.createL7Rule(newRule); + }); + + expect(createL7Rule).toHaveBeenCalledWith( + 'project1', + 'region1', + 'policy1', + newRule, + ); + expect(onSuccess).toHaveBeenCalled(); + }); +}); + +describe('useUpdateL7Rule', () => { + it('updates an existing L7 rule', async () => { + const updatedRule = { id: '1', key: 'key1', value: 'value1' } as TL7Rule; + vi.mocked(updateL7Rule).mockResolvedValueOnce(updatedRule); + + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useUpdateL7Rule({ + projectId: 'project1', + policyId: 'policy1', + region: 'region1', + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.updateL7Rule(updatedRule); + }); + + expect(updateL7Rule).toHaveBeenCalledWith( + 'project1', + 'region1', + 'policy1', + updatedRule, + ); + expect(onSuccess).toHaveBeenCalled(); + }); +}); + +describe('useGetL7Rule', () => { + it('fetches a single L7 rule', async () => { + const mockRule = { id: '1', key: 'key1', value: 'value1' } as TL7Rule; + vi.mocked(getL7Rule).mockResolvedValueOnce(mockRule); + + const { result } = renderHook( + () => useGetL7Rule('project1', 'policy1', 'region1', 'rule1'), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockRule); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Rule.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Rule.tsx index 8cd5919825c2..ca857d7e41f5 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Rule.tsx +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useL7Rule.tsx @@ -45,7 +45,7 @@ export const useL7Rules = ( () => ({ isLoading, isPending, - allL7Policies: allL7Rules, + allL7Rules, paginatedL7Rules: paginateResults( sortResults(applyFilters(allL7Rules || [], filters), sorting), pagination, diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useListener.spec.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/useListener.spec.tsx new file mode 100644 index 000000000000..83be69785b29 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useListener.spec.tsx @@ -0,0 +1,89 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { useListener, useDeleteListener } from './useListener'; +import { getListener, deleteListener } from '@/api/data/listener'; +import { wrapper } from '@/wrapperRenders'; +import { TLoadBalancerListener } from '../data/load-balancer'; + +vi.mock('@/api/data/listener'); + +describe('useListener', () => { + const projectId = 'test-project'; + const region = 'test-region'; + const loadBalancerId = 'test-loadbalancer'; + const listenerId = 'test-listener'; + + it('fetches listener data successfully', async () => { + vi.mocked(getListener).mockResolvedValueOnce({ + id: listenerId, + } as TLoadBalancerListener); + + const { result } = renderHook( + () => useListener({ projectId, region, loadBalancerId, listenerId }), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual({ id: listenerId }); + expect(getListener).toHaveBeenCalledWith(projectId, region, listenerId); + }); +}); + +describe('useDeleteListener', () => { + const projectId = 'test-project'; + const region = 'test-region'; + const loadBalancerId = 'test-loadbalancer'; + const listenerId = 'test-listener'; + const onError = vi.fn(); + const onSuccess = vi.fn(); + + it('deletes listener successfully', async () => { + vi.mocked(deleteListener).mockResolvedValueOnce({}); + + const { result } = renderHook( + () => + useDeleteListener({ + projectId, + region, + loadBalancerId, + listenerId, + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.deleteListener(); + }); + + expect(deleteListener).toHaveBeenCalledWith(projectId, region, listenerId); + expect(onSuccess).toHaveBeenCalled(); + }); + + it('handles error while deleting listener', async () => { + const error = new Error('Failed to delete'); + vi.mocked(deleteListener).mockRejectedValueOnce(error); + + const { result } = renderHook( + () => + useDeleteListener({ + projectId, + region, + loadBalancerId, + listenerId, + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.deleteListener(); + }); + + expect(deleteListener).toHaveBeenCalledWith(projectId, region, listenerId); + expect(onError).toHaveBeenCalled(); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useLoadBalancer.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/hook/useLoadBalancer.spec.ts index 3a4b61c7cd21..e87087c2a54f 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/hook/useLoadBalancer.spec.ts +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useLoadBalancer.spec.ts @@ -1,63 +1,408 @@ -import { renderHook, waitFor } from '@testing-library/react'; -import { UseQueryResult } from '@tanstack/react-query'; +import { renderHook, act, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; import { applyFilters } from '@ovh-ux/manager-core-api'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { ColumnSort, PaginationState } from '@tanstack/react-table'; -import { useAllLoadBalancers, useLoadBalancers } from '../hook/useLoadBalancer'; +import { + useAllLoadBalancers, + useLoadBalancers, + useLoadBalancer, + useLoadBalancerFlavor, + useDeleteLoadBalancer, + useRenameLoadBalancer, + useCreateListener, + useEditLoadBalancer, + useAllLoadBalancerListeners, + useLoadBalancerListeners, + useCreateLoadBalancer, + ListenerInfoProps, +} from './useLoadBalancer'; +import { + getLoadBalancers, + getLoadBalancer, + getLoadBalancerFlavor, + deleteLoadBalancer, + updateLoadBalancerName, + createListener, + editListener, + getLoadBalancerListeners, + createLoadBalancer, + TLoadBalancer, + TLoadBalancerListener, + TFlavor, + TLoadBalancerResponse, +} from '../data/load-balancer'; import { paginateResults, sortResults } from '@/helpers'; import { wrapper } from '@/wrapperRenders'; -import { mockLoadBalancers } from '@/mocks'; -import { TLoadBalancer } from '../data/load-balancer'; - -vi.mock('@/helpers', () => ({ - paginateResults: vi.fn(), - sortResults: vi.fn(), -})); - -vi.mock('./useLoadBalancer', async () => { - const mod = await vi.importActual('./useLoadBalancer'); - return { - ...mod, - useAllLoadBalancers: vi.fn(), - }; -}); +import { TPrivateNetwork, TSubnet } from '../data/network'; +import { TFloatingIp } from '../data/floating-ips'; +import { TRegion } from './useRegions'; + +vi.mock('../data/load-balancer'); + +describe('useLoadBalancer hooks', () => { + describe('useAllLoadBalancers', () => { + it('should fetch all load balancers', async () => { + const mockData = [{ id: '1', name: 'lb1' }] as TLoadBalancer[]; + vi.mocked(getLoadBalancers).mockResolvedValue({ + resources: mockData, + } as TLoadBalancerResponse); + + const { result } = renderHook(() => useAllLoadBalancers('projectId'), { + wrapper, + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); + }); + + describe('useLoadBalancers', () => { + it('should return paginated and sorted load balancers', async () => { + const mockData = [{ id: '1', name: 'lb1' }] as TLoadBalancer[]; + vi.mocked(getLoadBalancers).mockResolvedValue({ + resources: mockData, + } as TLoadBalancerResponse); + vi.mocked(applyFilters).mockReturnValue(mockData); + vi.mocked(sortResults).mockReturnValue(mockData); + vi.mocked(paginateResults).mockReturnValue({ + rows: mockData, + pageCount: 1, + totalRows: 1, + }); + + const pagination = { pageIndex: 0, pageSize: 10 }; + const sorting = { id: 'name', desc: false }; + const filters = []; + + const { result } = renderHook( + () => useLoadBalancers('projectId', pagination, sorting, filters), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isLoading).toBe(false)); -describe('useLoadBalancers', () => { - const projectId = 'test-project'; - const pagination: PaginationState = { pageIndex: 1, pageSize: 10 }; - const sorting: ColumnSort = { id: 'name', desc: false }; - const filters = []; - - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('should return data when loaded', async () => { - vi.mocked(applyFilters).mockReturnValue(mockLoadBalancers); - vi.mocked(sortResults).mockReturnValue(mockLoadBalancers); - vi.mocked(paginateResults).mockReturnValue({ - rows: mockLoadBalancers, - totalRows: 1, - pageCount: 1, - }); - vi.mocked(useAllLoadBalancers).mockReturnValue({ - data: mockLoadBalancers, - error: null, - isLoading: false, - isPending: false, - } as UseQueryResult); - - const { result } = renderHook( - () => useLoadBalancers(projectId, pagination, sorting, filters), - { wrapper }, - ); - - await waitFor(() => { expect(result.current.paginatedLoadBalancer).toEqual({ - rows: mockLoadBalancers, + rows: mockData, + pageCount: 1, + totalRows: 1, + }); + }); + }); + + describe('useLoadBalancer', () => { + it('should fetch a single load balancer', async () => { + const mockData = { id: '1', name: 'lb1' } as TLoadBalancer; + vi.mocked(getLoadBalancer).mockResolvedValue(mockData); + + const { result } = renderHook( + () => + useLoadBalancer({ + projectId: 'projectId', + region: 'region', + loadBalancerId: 'loadBalancerId', + }), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); + }); + + describe('useLoadBalancerFlavor', () => { + it('should fetch load balancer flavor', async () => { + const mockData = { id: '1', name: 'flavor1' } as TFlavor; + vi.mocked(getLoadBalancerFlavor).mockResolvedValue(mockData); + + const { result } = renderHook( + () => + useLoadBalancerFlavor({ + projectId: 'projectId', + region: 'region', + flavorId: 'flavorId', + }), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); + }); + + describe('useDeleteLoadBalancer', () => { + it('should delete a load balancer', async () => { + vi.mocked(deleteLoadBalancer).mockResolvedValue({} as never); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useDeleteLoadBalancer({ + projectId: 'projectId', + loadBalancer: { id: '1', name: 'lb1' } as TLoadBalancer, + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + await result.current.deleteLoadBalancer(); + }); + + expect(deleteLoadBalancer).toHaveBeenCalledWith('projectId', { + id: '1', + name: 'lb1', + }); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + describe('useRenameLoadBalancer', () => { + it('should rename a load balancer', async () => { + vi.mocked(updateLoadBalancerName).mockResolvedValue({}); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useRenameLoadBalancer({ + projectId: 'projectId', + loadBalancer: { id: '1', name: 'lb1' } as TLoadBalancer, + name: 'newName', + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + await result.current.renameLoadBalancer(); + }); + + expect(updateLoadBalancerName).toHaveBeenCalledWith( + 'projectId', + { id: '1', name: 'lb1' }, + 'newName', + ); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + describe('useCreateListener', () => { + it('should create a listener', async () => { + vi.mocked(createListener).mockResolvedValue({}); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useCreateListener({ + projectId: 'projectId', + region: 'region', + loadBalancerId: 'loadBalancerId', + onError, + onSuccess, + }), + { wrapper }, + ); + + const listenerInfo = { + name: 'listener1', + protocol: 'http', + port: 80, + } as ListenerInfoProps; + + await act(async () => { + await result.current.createListener(listenerInfo); + }); + + expect(createListener).toHaveBeenCalledWith({ + projectId: 'projectId', + region: 'region', + loadBalancerId: 'loadBalancerId', + ...listenerInfo, + }); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + describe('useEditLoadBalancer', () => { + it('should edit a listener', async () => { + vi.mocked(editListener).mockResolvedValue({}); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useEditLoadBalancer({ + projectId: 'projectId', + region: 'region', + listenerId: 'listenerId', + onError, + onSuccess, + }), + { wrapper }, + ); + + const listenerInfo = { + name: 'listener1', + defaultPoolId: 'poolId', + }; + + await act(async () => { + await result.current.editListener(listenerInfo); + }); + + expect(editListener).toHaveBeenCalledWith({ + projectId: 'projectId', + region: 'region', + listenerId: 'listenerId', + ...listenerInfo, + }); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + describe('useAllLoadBalancerListeners', () => { + it('should fetch all load balancer listeners', async () => { + const mockData = [ + { id: '1', name: 'listener1' }, + ] as TLoadBalancerListener[]; + vi.mocked(getLoadBalancerListeners).mockResolvedValue(mockData); + + const { result } = renderHook( + () => + useAllLoadBalancerListeners({ + projectId: 'projectId', + region: 'region', + loadBalancerId: 'loadBalancerId', + }), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual( + mockData.map((listener) => ({ + ...listener, + search: `${listener.name} ${listener.defaultPoolId} ${listener.protocol} ${listener.port}`, + })), + ); + }); + }); + + describe('useLoadBalancerListeners', () => { + it('should return paginated and sorted load balancer listeners', async () => { + const mockData = [ + { id: '1', name: 'listener1' }, + ] as TLoadBalancerListener[]; + vi.mocked(getLoadBalancerListeners).mockResolvedValue(mockData); + vi.mocked(applyFilters).mockReturnValue(mockData); + vi.mocked(sortResults).mockReturnValue(mockData); + vi.mocked(paginateResults).mockReturnValue({ + rows: mockData, + pageCount: 1, totalRows: 1, + }); + + const pagination = { pageIndex: 0, pageSize: 10 }; + const sorting = { id: 'name', desc: false }; + const filters = []; + + const { result } = renderHook( + () => + useLoadBalancerListeners( + 'projectId', + 'region', + 'loadBalancerId', + pagination, + sorting, + filters, + ), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isLoading).toBe(false)); + + expect(result.current.data).toEqual({ + rows: mockData, pageCount: 1, + totalRows: 1, + }); + }); + }); + + describe('useCreateLoadBalancer', () => { + it('should create a load balancer', async () => { + vi.mocked(createLoadBalancer).mockResolvedValue({}); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useCreateLoadBalancer({ + projectId: 'projectId', + flavor: { + id: '1', + name: 'flavor', + } as TFlavor, + region: { + name: 'region', + } as TRegion, + floatingIp: { + id: '1', + ip: '1.1.1.1', + } as TFloatingIp, + privateNetwork: { + id: '1', + name: 'privateNetwork', + } as TPrivateNetwork, + subnet: { + id: '1', + name: 'subnet', + } as TSubnet, + gateways: [], + listeners: [], + name: 'lb1', + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + await result.current.doCreateLoadBalancer(); + }); + + expect(createLoadBalancer).toHaveBeenCalledWith({ + projectId: 'projectId', + flavor: { + id: '1', + name: 'flavor', + }, + region: { + name: 'region', + }, + floatingIp: { + id: '1', + ip: '1.1.1.1', + }, + privateNetwork: { + id: '1', + name: 'privateNetwork', + }, + subnet: { + id: '1', + name: 'subnet', + }, + gateways: [], + listeners: [], + name: 'lb1', }); + expect(onSuccess).toHaveBeenCalled(); }); }); }); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useNetwork.spec.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/useNetwork.spec.tsx new file mode 100644 index 000000000000..a6bfe6ff3baf --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useNetwork.spec.tsx @@ -0,0 +1,104 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { + usePrivateNetworkByRegion, + useSubnetByNetworkAndRegion, + useGetPrivateNetworks, + useGetPrivateNetworkSubnets, + useGetRegionPrivateNetworks, +} from './useNetwork'; +import { + getPrivateNetworkByRegion, + getPrivateNetworks, + getPrivateNetworkSubnets, + getRegionPrivateNetworks, + getSubnetByNetworkAndRegion, + TNetwork, + TPrivateNetwork, + TSubnet, +} from '../data/network'; +import { wrapper } from '@/wrapperRenders'; + +vi.mock('../data/network'); + +describe('useNetwork hooks', () => { + it('usePrivateNetworkByRegion should fetch private network by region', async () => { + const mockData = { id: 'network-1' } as TNetwork; + vi.mocked(getPrivateNetworkByRegion).mockResolvedValue(mockData); + + const { result } = renderHook( + () => + usePrivateNetworkByRegion({ + projectId: 'project-1', + region: 'region-1', + networkId: 'network-1', + }), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); + + it('useSubnetByNetworkAndRegion should fetch subnet by network and region', async () => { + const mockData = { id: 'subnet-1' } as TSubnet; + vi.mocked(getSubnetByNetworkAndRegion).mockResolvedValue(mockData); + + const { result } = renderHook( + () => + useSubnetByNetworkAndRegion({ + projectId: 'project-1', + region: 'region-1', + networkId: 'network-1', + subnetId: 'subnet-1', + }), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); + + it('useGetPrivateNetworks should fetch private networks', async () => { + const mockData = [{ id: 'network-1' }] as TPrivateNetwork[]; + vi.mocked(getPrivateNetworks).mockResolvedValue(mockData); + + const { result } = renderHook(() => useGetPrivateNetworks('project-1'), { + wrapper, + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); + + it('useGetPrivateNetworkSubnets should fetch private network subnets', async () => { + const mockData = [{ id: 'subnet-1' }] as TSubnet[]; + vi.mocked(getPrivateNetworkSubnets).mockResolvedValue(mockData); + + const { result } = renderHook( + () => useGetPrivateNetworkSubnets('project-1', 'region-1', 'network-1'), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); + + it('useGetRegionPrivateNetworks should fetch region private networks', async () => { + const mockData = [{ id: 'network-1' }] as TPrivateNetwork[]; + vi.mocked(getRegionPrivateNetworks).mockResolvedValue(mockData); + + const { result } = renderHook( + () => useGetRegionPrivateNetworks('project-1', 'region-1'), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/usePlans.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/hook/usePlans.spec.ts new file mode 100644 index 000000000000..f471b92a059b --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/usePlans.spec.ts @@ -0,0 +1,30 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { IMe, useMe } from '@ovh-ux/manager-react-components'; +import { vi } from 'vitest'; +import { useGetPlans } from './usePlans'; +import { getPlans, TPlanResponse } from '../data/plans'; +import { wrapper } from '@/wrapperRenders'; + +vi.mock('../data/plans', () => ({ + getPlans: vi.fn(), +})); + +describe('useGetPlans', () => { + it('should fetch plans when me is available', async () => { + const mockMe = { ovhSubsidiary: 'US' } as IMe; + const plansResponse = { + plans: [{ code: 'Plan 1' }], + } as TPlanResponse; + vi.mocked(useMe).mockReturnValue({ me: mockMe }); + vi.mocked(getPlans).mockResolvedValue(plansResponse); + + const { result } = renderHook(() => useGetPlans('project-id'), { + wrapper, + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(plansResponse); + expect(getPlans).toHaveBeenCalledWith('project-id', 'US'); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/usePool.spec.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/usePool.spec.tsx new file mode 100644 index 000000000000..583bf6c88e60 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/usePool.spec.tsx @@ -0,0 +1,175 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { + useAllLoadBalancerPools, + useGetPool, + useDeletePool, + useCreatePool, + useUpdatePool, +} from './usePool'; +import { + getLoadBalancerPools, + getPool, + deletePool, + createPool, + updatePool, + TLoadBalancerPool, +} from '@/api/data/pool'; +import { wrapper } from '@/wrapperRenders'; + +vi.mock('@/api/data/pool'); + +describe('usePool hooks', () => { + it('should fetch all load balancer pools', async () => { + const mockData = [ + { + id: '1', + name: 'pool1', + algorithm: 'round-robin', + protocol: 'http', + search: 'pool1 round-robin http', + }, + ] as TLoadBalancerPool[]; + vi.mocked(getLoadBalancerPools).mockResolvedValue(mockData); + + const { result } = renderHook( + () => + useAllLoadBalancerPools({ + projectId: '1', + region: 'us', + loadBalancerId: 'lb1', + }), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); + + it('should fetch a single pool', async () => { + const mockPool = { + id: '1', + name: 'pool1', + algorithm: 'round-robin', + protocol: 'http', + } as TLoadBalancerPool; + vi.mocked(getPool).mockResolvedValue(mockPool); + + const { result } = renderHook( + () => useGetPool({ projectId: '1', region: 'us', poolId: '1' }), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockPool); + }); + + it('should delete a pool', async () => { + vi.mocked(deletePool).mockResolvedValue({}); + + const onSuccess = vi.fn(); + const onError = vi.fn(); + + const { result } = renderHook( + () => useDeletePool({ projectId: '1', region: 'us', onError, onSuccess }), + { wrapper }, + ); + + await act(async () => { + result.current.deletePool('1'); + }); + + expect(deletePool).toHaveBeenCalledWith('1', 'us', '1'); + expect(onSuccess).toHaveBeenCalled(); + }); + + it('should create a pool', async () => { + const mockPool = { + id: '1', + name: 'pool1', + algorithm: 'round-robin', + protocol: 'http', + } as TLoadBalancerPool; + vi.mocked(createPool).mockResolvedValue(mockPool); + + const onSuccess = vi.fn(); + const onError = vi.fn(); + + const { result } = renderHook( + () => + useCreatePool({ + projectId: '1', + region: 'us', + loadbalancerId: 'lb1', + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.doCreatePool({ + name: 'pool1', + algorithm: 'round-robin', + protocol: 'http', + permanentSession: { isEnabled: true }, + }); + }); + + expect(createPool).toHaveBeenCalledWith({ + projectId: '1', + region: 'us', + loadbalancerId: 'lb1', + name: 'pool1', + algorithm: 'round-robin', + protocol: 'http', + permanentSession: { isEnabled: true }, + }); + expect(onSuccess).toHaveBeenCalledWith(mockPool); + }); + + it('should update a pool', async () => { + const mockPool = { + id: '1', + name: 'pool1', + algorithm: 'round-robin', + protocol: 'http', + } as TLoadBalancerPool; + vi.mocked(updatePool).mockResolvedValue(mockPool); + + const onSuccess = vi.fn(); + const onError = vi.fn(); + + const { result } = renderHook( + () => + useUpdatePool({ + projectId: '1', + region: 'us', + poolId: '1', + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + result.current.doUpdatePool({ + name: 'pool1', + algorithm: 'round-robin', + permanentSession: { isEnabled: true }, + }); + }); + + expect(updatePool).toHaveBeenCalledWith({ + projectId: '1', + region: 'us', + poolId: '1', + name: 'pool1', + algorithm: 'round-robin', + permanentSession: { isEnabled: true }, + }); + expect(onSuccess).toHaveBeenCalledWith(mockPool); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/usePoolMember.spec.tsx b/packages/manager/apps/pci-load-balancer/src/api/hook/usePoolMember.spec.tsx new file mode 100644 index 000000000000..d9fce89ff5be --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/usePoolMember.spec.tsx @@ -0,0 +1,220 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { applyFilters } from '@ovh-ux/manager-core-api'; +import { + useGetAllPoolMembers, + usePoolMembers, + useDeletePoolMember, + useGetPoolMember, + useUpdatePoolMember, + useCreatePoolMembers, +} from './usePoolMember'; +import { + getPoolMembers, + getPoolMember, + deletePoolMember, + updatePoolMemberName, + createPoolMembers, + TPoolMember, +} from '@/api/data/pool-member'; +import { wrapper } from '@/wrapperRenders'; +import { sortResults, paginateResults } from '@/helpers'; + +vi.mock('@/api/data/pool-member', () => ({ + getPoolMembers: vi.fn(), + getPoolMember: vi.fn(), + deletePoolMember: vi.fn(), + updatePoolMemberName: vi.fn(), + createPoolMembers: vi.fn(), +})); + +describe('usePoolMember hooks', () => { + describe('useGetAllPoolMembers', () => { + it('should fetch all pool members', async () => { + const mockData = [ + { id: '1', name: 'member1', address: '127.0.0.1', protocolPort: 80 }, + ] as TPoolMember[]; + vi.mocked(getPoolMembers).mockResolvedValue(mockData); + + const { result } = renderHook( + () => useGetAllPoolMembers('projectId', 'poolId', 'region'), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual( + mockData.map((member) => ({ + ...member, + search: `${member.name} ${member.address} ${member.protocolPort}`, + })), + ); + }); + }); + + describe('usePoolMembers', () => { + it('should return paginated and sorted pool members', async () => { + const mockData = [ + { id: '1', name: 'member1', address: '127.0.0.1', protocolPort: 80 }, + ] as TPoolMember[]; + vi.mocked(getPoolMembers).mockResolvedValue(mockData); + vi.mocked(applyFilters).mockReturnValue(mockData); + vi.mocked(sortResults).mockReturnValue(mockData); + vi.mocked(paginateResults).mockReturnValue({ + rows: mockData, + pageCount: 1, + totalRows: 1, + }); + + const pagination = { pageIndex: 0, pageSize: 10 }; + const sorting = { id: 'name', desc: false }; + const filters = []; + + const { result } = renderHook( + () => + usePoolMembers( + 'projectId', + 'policyId', + 'region', + pagination, + sorting, + filters, + ), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isLoading).toBe(false)); + + expect(result.current.paginatedPoolMembers).toEqual({ + rows: mockData, + pageCount: 1, + totalRows: 1, + }); + }); + }); + + describe('useDeletePoolMember', () => { + it('should delete a pool member', async () => { + vi.mocked(deletePoolMember).mockResolvedValue({}); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useDeletePoolMember({ + projectId: 'projectId', + poolId: 'poolId', + memberId: 'memberId', + region: 'region', + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + await result.current.deletePoolMember(); + }); + + expect(deletePoolMember).toHaveBeenCalledWith( + 'projectId', + 'region', + 'poolId', + 'memberId', + ); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + describe('useGetPoolMember', () => { + it('should fetch a single pool member', async () => { + const mockData = { + id: '1', + name: 'member1', + address: '127.0.0.1', + protocolPort: 80, + } as TPoolMember; + vi.mocked(getPoolMember).mockResolvedValue(mockData); + + const { result } = renderHook( + () => useGetPoolMember('projectId', 'poolId', 'region', 'memberId'), + { wrapper }, + ); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual(mockData); + }); + }); + + describe('useUpdatePoolMember', () => { + it('should update a pool member name', async () => { + vi.mocked(updatePoolMemberName).mockResolvedValue({}); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useUpdatePoolMember({ + projectId: 'projectId', + poolId: 'poolId', + memberId: 'memberId', + region: 'region', + name: 'newName', + onError, + onSuccess, + }), + { wrapper }, + ); + + await act(async () => { + await result.current.updatePoolMemberName(); + }); + + expect(updatePoolMemberName).toHaveBeenCalledWith( + 'projectId', + 'region', + 'poolId', + 'memberId', + 'newName', + ); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + describe('useCreatePoolMembers', () => { + it('should create pool members', async () => { + vi.mocked(createPoolMembers).mockResolvedValue({}); + const onError = vi.fn(); + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => + useCreatePoolMembers({ + projectId: 'projectId', + poolId: 'poolId', + region: 'region', + onError, + onSuccess, + }), + { wrapper }, + ); + + const newMembers = [ + { id: '2', name: 'member2', address: '127.0.0.2', protocolPort: 81 }, + ] as TPoolMember[]; + + await act(async () => { + await result.current.createPoolMembers(newMembers); + }); + + expect(createPoolMembers).toHaveBeenCalledWith( + 'projectId', + 'region', + 'poolId', + newMembers, + ); + expect(onSuccess).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useRegions.spec.ts b/packages/manager/apps/pci-load-balancer/src/api/hook/useRegions.spec.ts new file mode 100644 index 000000000000..d4a36197855d --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useRegions.spec.ts @@ -0,0 +1,81 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { vi } from 'vitest'; +import { UseQueryResult } from '@tanstack/react-query'; +import { useGetRegions } from './useRegions'; +import { useGetPlans } from '@/api/hook/usePlans'; +import { useGetPrivateNetworks } from '@/api/hook/useNetwork'; +import { TPrivateNetwork } from '../data/network'; +import { TPlanResponse } from '../data/plans'; + +vi.mock('@/api/hook/usePlans'); +vi.mock('@/api/hook/useNetwork'); + +describe('useGetRegions', () => { + const projectId = 'test-project-id'; + + it('should return regions data when plans and networks are available', async () => { + const sizeFlavor = 's'; + const plansResponse = { + plans: [ + { + code: `octavia-loadbalancer.loadbalancer-${sizeFlavor}.hour.consumption`, + regions: [ + { name: 'region1', continentCode: 'EU' }, + { name: 'region2', continentCode: 'NA' }, + ], + }, + ], + }; + const networks = [ + { + regions: [{ region: 'region1' }], + }, + ]; + + vi.mocked(useGetPlans).mockReturnValue({ + data: plansResponse, + isPending: false, + } as UseQueryResult); + vi.mocked(useGetPrivateNetworks).mockReturnValue({ + data: networks, + isPending: false, + } as UseQueryResult); + + const { result } = renderHook(() => useGetRegions(projectId)); + + await waitFor(() => expect(result.current.isPending).toBe(false)); + expect(result.current.data.get(sizeFlavor)).toEqual([ + { + continent: 'manager_components_region_continent_region', + continentCode: 'EU', + isEnabled: true, + macroName: 'manager_components_region_region', + microName: 'manager_components_region_region_micro', + name: 'region1', + }, + { + name: 'region2', + continent: 'manager_components_region_continent_region', + isEnabled: false, + continentCode: 'NA', + macroName: 'manager_components_region_region', + microName: 'manager_components_region_region_micro', + }, + ]); + }); + + it('should handle empty plans and networks', async () => { + vi.mocked(useGetPlans).mockReturnValue({ + data: { plans: [] }, + isPending: false, + } as UseQueryResult); + vi.mocked(useGetPrivateNetworks).mockReturnValue({ + data: [], + isPending: false, + } as UseQueryResult); + + const { result } = renderHook(() => useGetRegions(projectId)); + await waitFor(() => expect(result.current.isPending).toBe(false)); + expect(result.current.data.size).toBe(0); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/components/form/Label.component.spec.tsx b/packages/manager/apps/pci-load-balancer/src/components/form/Label.component.spec.tsx new file mode 100644 index 000000000000..c8cde8d8e6f0 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/components/form/Label.component.spec.tsx @@ -0,0 +1,37 @@ +import { render } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import LabelComponent from './Label.component'; + +describe('LabelComponent', () => { + it('renders the label text', () => { + const { getByText } = render(); + expect(getByText('Test Label')).toBeInTheDocument(); + }); + + it('renders the help text when provided', () => { + const { getByText } = render( + , + ); + expect(getByText('Help Text')).toBeInTheDocument(); + }); + + it('does not render the help text when not provided', () => { + const { queryByText } = render(); + expect(queryByText('Help Text')).not.toBeInTheDocument(); + }); + + it('applies error color when hasError is true', () => { + const { getByText } = render(); + const label = getByText('Test Label'); + expect(label).toHaveAttribute('color', 'error'); + }); + + it('applies custom className when provided', () => { + const { getByText } = render( + , + ); + + const { parentElement } = getByText('Test Label'); + expect(parentElement).toHaveClass('custom-class'); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/components/form/PolicyForm.component.spec.tsx b/packages/manager/apps/pci-load-balancer/src/components/form/PolicyForm.component.spec.tsx new file mode 100644 index 000000000000..6740df82f3d1 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/components/form/PolicyForm.component.spec.tsx @@ -0,0 +1,62 @@ +import { render, fireEvent } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import PolicyForm from './PolicyForm.component'; +import { TL7Policy } from '@/api/data/l7Policies'; +import { TLoadBalancerPool } from '@/api/data/pool'; +import { wrapper } from '@/wrapperRenders'; +import { TLoadBalancerListener } from '@/api/data/load-balancer'; + +const mockPolicy = { + listenerId: 'listener-1', + position: 1, + redirectHttpCode: undefined, + redirectPoolId: undefined, + redirectPrefix: undefined, + redirectUrl: undefined, + name: 'Test Policy', + action: '', +} as TL7Policy; + +const mockPools = [ + { id: 'pool-1', name: 'Pool 1', protocol: 'http' }, + { id: 'pool-2', name: 'Pool 2', protocol: 'https' }, +] as TLoadBalancerPool[]; + +const mockListener = { + id: 'listener-1', + protocol: 'http', +} as TLoadBalancerListener; + +describe('PolicyForm', () => { + it('renders the form with initial values', () => { + const { getByTestId } = render( + , + { wrapper }, + ); + + expect(getByTestId('policyForm-name_input')).toHaveValue('Test Policy'); + }); + + it('calls onCancel when cancel button is clicked', () => { + const onCancel = vi.fn(); + const { getByTestId } = render( + , + ); + + fireEvent.click(getByTestId('policyForm-cancel_button')); + + expect(onCancel).toHaveBeenCalled(); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/components/form/PolicyForm.component.tsx b/packages/manager/apps/pci-load-balancer/src/components/form/PolicyForm.component.tsx index f5ca19d85997..ca54177610da 100644 --- a/packages/manager/apps/pci-load-balancer/src/components/form/PolicyForm.component.tsx +++ b/packages/manager/apps/pci-load-balancer/src/components/form/PolicyForm.component.tsx @@ -66,7 +66,7 @@ export default function PolicyForm({ return pools?.filter( (pool) => !UNAVAILABLE_POOL_PROTOCOLS.includes(pool.protocol) && - LISTENER_POOL_PROTOCOL_COMBINATION[listener?.protocol].includes( + LISTENER_POOL_PROTOCOL_COMBINATION[listener?.protocol]?.includes( pool.protocol, ), ); @@ -161,6 +161,7 @@ export default function PolicyForm({ /> { @@ -365,6 +366,7 @@ export default function PolicyForm({ className="mr-4" color={ODS_THEME_COLOR_INTENT.primary} variant={ODS_BUTTON_VARIANT.stroked} + data-testid="policyForm-cancel_button" onClick={onCancel} > {t('octavia_load_balancer_create_l7_policy_cancel')} @@ -372,6 +374,7 @@ export default function PolicyForm({ onSubmit(policyState)} > {submitButtonText || diff --git a/packages/manager/apps/pci-load-balancer/src/components/form/RuleForm.component.tsx b/packages/manager/apps/pci-load-balancer/src/components/form/RuleForm.component.tsx index 71ba72d7419e..4a8db62693e6 100644 --- a/packages/manager/apps/pci-load-balancer/src/components/form/RuleForm.component.tsx +++ b/packages/manager/apps/pci-load-balancer/src/components/form/RuleForm.component.tsx @@ -217,7 +217,7 @@ export default function RuleForm({ {t('octavia_load_balancer_create_l7_rule_compare_type_default')} - {listCompareType.map((compareType) => ( + {listCompareType?.map((compareType) => ( {compareType.label} @@ -326,6 +326,7 @@ export default function RuleForm({ color={ODS_THEME_COLOR_INTENT.primary} disabled={isDisabled || undefined} onClick={() => onSubmit(formState)} + data-testid="ruleForm-submit_button" > {submitButtonText || t('octavia_load_balancer_create_l7_rule_submit')} diff --git a/packages/manager/apps/pci-load-balancer/src/components/form/Ruleform.component.spec.tsx b/packages/manager/apps/pci-load-balancer/src/components/form/Ruleform.component.spec.tsx new file mode 100644 index 000000000000..50d094e8b657 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/components/form/Ruleform.component.spec.tsx @@ -0,0 +1,39 @@ +import { render, fireEvent, act } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import RuleForm from './RuleForm.component'; +import { wrapper } from '@/wrapperRenders'; +import { TL7Rule } from '@/api/data/l7Rules'; + +describe('RuleForm Component', () => { + const onSubmit = vi.fn(); + const onCancel = vi.fn(); + it('should render the form', () => { + const { getByText } = render( + , + { + wrapper, + }, + ); + expect( + getByText('octavia_load_balancer_create_l7_rule_type'), + ).toBeInTheDocument(); + }); + + it('should call onSubmit when form is submitted', () => { + const handleSubmit = vi.fn(); + const rule = { + key: 'key', + value: 'value', + ruleType: 'ruleType', + compareType: 'compareType', + } as TL7Rule; + const { getByTestId } = render( + , + ); + const submitButton = getByTestId('ruleForm-submit_button'); + act(() => { + fireEvent.click(submitButton); + }); + expect(handleSubmit).toHaveBeenCalled(); + }); +}); diff --git a/packages/manager/apps/pci-load-balancer/src/helpers/index.spec.ts b/packages/manager/apps/pci-load-balancer/src/helpers/index.spec.ts index 9cf06f1d5ea1..27da7bde8a3a 100644 --- a/packages/manager/apps/pci-load-balancer/src/helpers/index.spec.ts +++ b/packages/manager/apps/pci-load-balancer/src/helpers/index.spec.ts @@ -1,7 +1,9 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { paginateResults, sortResults } from './index'; import { TLoadBalancer } from '@/api/data/load-balancer'; +vi.unmock('@/helpers'); + describe('paginateResults', () => { it('returns correct pagination data for given items and pagination state', () => { const items = Array.from({ length: 50 }, (_, i) => ({ id: i })); diff --git a/packages/manager/apps/pci-load-balancer/src/mocks/index.ts b/packages/manager/apps/pci-load-balancer/src/mocks/index.ts index 811c6b954132..cc41a7ef8046 100644 --- a/packages/manager/apps/pci-load-balancer/src/mocks/index.ts +++ b/packages/manager/apps/pci-load-balancer/src/mocks/index.ts @@ -1,17 +1,31 @@ -import { TLoadBalancer } from '@/api/data/load-balancer'; +import { TFlavor } from '@ovh-ux/manager-pci-common'; +import { TLoadBalancer, TLoadBalancerListener } from '@/api/data/load-balancer'; -export const mockLoadBalancers = [ - { - id: 'idLoadBalancer1', - name: 'mockLoadBalancer1', - createdAt: '2024-07-15T12:13:04Z', - updatedAt: '2024-07-15T12:15:03Z', - flavorId: 'flavor', - operatingStatus: 'online', - provisioningStatus: 'active', - vipAddress: '1.1.1.1', - vipNetworkId: 'networkId', - vipSubnetId: 'subnetId', - region: 'region', - }, -] as TLoadBalancer[]; +export const mockLoadBalancer = { + id: 'idLoadBalancer1', + name: 'mockLoadBalancer1', + createdAt: '2024-07-15T12:13:04Z', + updatedAt: '2024-07-15T12:15:03Z', + flavorId: 'flavor', + operatingStatus: 'online', + provisioningStatus: 'active', + vipAddress: '1.1.1.1', + vipNetworkId: 'networkId', + vipSubnetId: 'subnetId', + region: 'region', +} as TLoadBalancer; +export const mockLoadBalancers = [mockLoadBalancer] as TLoadBalancer[]; + +export const mockFlavor = { + id: 'idFlavoer', + name: 'flavorName', + region: 'region', +} as TFlavor; + +export const mockLoadBalancerListener = { + id: 'id1', + name: 'listener1', + description: 'description', +} as TLoadBalancerListener; + +export const mockLoadBalancerListeners = [mockLoadBalancerListener]; diff --git a/packages/manager/apps/pci-load-balancer/src/setupTests.tsx b/packages/manager/apps/pci-load-balancer/src/setupTests.tsx index d6a25ff5ae63..12a2aa94d5bc 100644 --- a/packages/manager/apps/pci-load-balancer/src/setupTests.tsx +++ b/packages/manager/apps/pci-load-balancer/src/setupTests.tsx @@ -27,6 +27,7 @@ vi.mock('@ovh-ux/manager-pci-common', async () => { description: 'project-description', }, }), + useCatalog: vi.fn(), }; }); @@ -49,6 +50,11 @@ vi.mock('@ovh-ux/manager-react-components', async () => { addError: vi.fn(), addSuccess: vi.fn(), }), + useMe: vi.fn().mockReturnValue({ + me: { + ovhSubsidiary: 'FR', + }, + }), }; }); @@ -89,3 +95,12 @@ vi.mock('@ovh-ux/manager-core-api', async () => { }, }; }); + +vi.mock('@/helpers', async () => { + const actual = await vi.importActual('@/helpers'); + return { + ...actual, + paginateResults: vi.fn(), + sortResults: vi.fn(), + }; +}); diff --git a/packages/manager/apps/pci-load-balancer/vitest.config.js b/packages/manager/apps/pci-load-balancer/vitest.config.js index 973457c1850b..73800903f374 100644 --- a/packages/manager/apps/pci-load-balancer/vitest.config.js +++ b/packages/manager/apps/pci-load-balancer/vitest.config.js @@ -12,20 +12,18 @@ export default defineConfig({ coverage: { include: ['src'], exclude: [ - 'src/interface', - 'src/__tests__', 'src/**/*constants.ts', 'src/**/*enum.ts', 'src/vite-*.ts', 'src/App.tsx', 'src/core/ShellRoutingSync.tsx', 'src/core/HidePreloader.tsx', - 'src/i18n.ts', 'src/main.tsx', 'src/pages/Layout.tsx', 'src/routes.tsx', 'src/queryClient.ts', 'src/wrapperRenders.tsx', + 'src/mocks/index.ts', ], }, }, diff --git a/packages/manager/apps/pci-private-registry/src/pages/create/steps/__snapshots__/PlanStep.spec.tsx.snap b/packages/manager/apps/pci-private-registry/src/pages/create/steps/__snapshots__/PlanStep.spec.tsx.snap index 7da6e0eb4af8..1282e7646d9d 100644 --- a/packages/manager/apps/pci-private-registry/src/pages/create/steps/__snapshots__/PlanStep.spec.tsx.snap +++ b/packages/manager/apps/pci-private-registry/src/pages/create/steps/__snapshots__/PlanStep.spec.tsx.snap @@ -27,9 +27,13 @@ exports[`PlanStep > should render 1`] = ` class="flex flex-col md:flex-row" >
- private_registry_create_choose_plan +
+ private_registry_create_choose_plan +
diff --git a/packages/manager/modules/manager-pci-common/src/components/quantity-selector/QuantitySelector.component.tsx b/packages/manager/modules/manager-pci-common/src/components/quantity-selector/QuantitySelector.component.tsx index 7886a9697bf9..c524b9e0a497 100644 --- a/packages/manager/modules/manager-pci-common/src/components/quantity-selector/QuantitySelector.component.tsx +++ b/packages/manager/modules/manager-pci-common/src/components/quantity-selector/QuantitySelector.component.tsx @@ -20,7 +20,7 @@ import { ODS_INPUT_TYPE, } from '@ovhcloud/ods-components'; import { useTranslation } from 'react-i18next'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import '../../translations/quantity-selector'; @@ -101,6 +101,7 @@ export function QuantitySelector({ variant={ODS_BUTTON_VARIANT.flat} color={ODS_THEME_COLOR_INTENT.primary} size={ODS_BUTTON_SIZE.sm} + data-testid="quantity-button-minus" text-align="center" disabled={min !== undefined && value - 1 < min ? true : undefined} > @@ -130,6 +131,7 @@ export function QuantitySelector({ variant={ODS_BUTTON_VARIANT.flat} color={ODS_THEME_COLOR_INTENT.primary} size={ODS_BUTTON_SIZE.sm} + data-testid="quantity-button-plus" text-align="center" disabled={max !== undefined && value + 1 > max ? true : undefined} >