diff --git a/public/locales/gsa-de.json b/public/locales/gsa-de.json index 66a2d5fe54..aded6f0ab8 100644 --- a/public/locales/gsa-de.json +++ b/public/locales/gsa-de.json @@ -1604,6 +1604,7 @@ "Port Counts: Total": "Portanzahl: Gesamt", "Port Counts: UDP": "Portanzahl: UDP", "Port List": "Portliste", + "Port List imported successfully.": "Portliste erfolgreich importiert.", "Port List: {{name}}": "Portliste: {{name}}", "Port Lists": "Portlisten", "Port Lists Filter": "Portlisten-Filter", diff --git a/public/locales/gsa-en.json b/public/locales/gsa-en.json index 31e7021425..c8492eadfe 100644 --- a/public/locales/gsa-en.json +++ b/public/locales/gsa-en.json @@ -1604,6 +1604,7 @@ "Port Counts: Total": "Port Counts: Total", "Port Counts: UDP": "Port Counts: UDP", "Port List": "Port List", + "Port List imported successfully.": "Port List imported successfully.", "Port List: {{name}}": "Port List: {{name}}", "Port Lists": "Port Lists", "Port Lists Filter": "Port Lists Filter", diff --git a/public/locales/gsa-it.json b/public/locales/gsa-it.json index cd4b0d1dd6..5d74f3c008 100644 --- a/public/locales/gsa-it.json +++ b/public/locales/gsa-it.json @@ -1604,6 +1604,8 @@ "Port Counts: Total": "Conteggi porte: Totale", "Port Counts: UDP": "Conteggi porte: UDP", "Port List": "Elenco porte", + "Port List imported successfully.": "Elenco porte importato con successo.", + "Port List: {{name}}": "Elenco porte: {{name}}", "Port Lists": "Elenchi porte", "Port Lists Filter": "Filtro elenchi porte", diff --git a/public/locales/gsa-ja.json b/public/locales/gsa-ja.json index 8522cfe63a..1fdd25dc02 100644 --- a/public/locales/gsa-ja.json +++ b/public/locales/gsa-ja.json @@ -1604,6 +1604,7 @@ "Port Counts: Total": "ポート数: 合計", "Port Counts: UDP": "ポート数: UDP", "Port List": "ポートリスト", + "Port List imported successfully.": "", "Port List: {{name}}": "ポートリスト: {{name}}", "Port Lists": "ポートリスト", "Port Lists Filter": "ポートリストフィルター", diff --git a/public/locales/gsa-zh_CN.json b/public/locales/gsa-zh_CN.json index cd3d603a88..70e9d5ab4b 100644 --- a/public/locales/gsa-zh_CN.json +++ b/public/locales/gsa-zh_CN.json @@ -1604,6 +1604,7 @@ "Port Counts: Total": "端口数量: 总数", "Port Counts: UDP": "端口数量: UDP", "Port List": "端口列表", + "Port List imported successfully.": "", "Port List: {{name}}": "端口列表:{{name}}", "Port Lists": "端口列表", "Port Lists Filter": "端口列表筛选", diff --git a/public/locales/gsa-zh_TW.json b/public/locales/gsa-zh_TW.json index d693528400..38ba130500 100644 --- a/public/locales/gsa-zh_TW.json +++ b/public/locales/gsa-zh_TW.json @@ -1604,6 +1604,7 @@ "Port Counts: Total": "", "Port Counts: UDP": "", "Port List": "連接埠清單", + "Port List imported successfully.": "", "Port List: {{name}}": "", "Port Lists": "連接埠清單", "Port Lists Filter": "", diff --git a/src/gmp/commands/__tests__/port-list.test.ts b/src/gmp/commands/__tests__/port-list.test.ts index db67b84d17..f7aa7dfa9f 100644 --- a/src/gmp/commands/__tests__/port-list.test.ts +++ b/src/gmp/commands/__tests__/port-list.test.ts @@ -8,7 +8,6 @@ import PortListCommand, {FROM_FILE} from 'gmp/commands/port-list'; import { createHttp, createActionResultResponse, - createHttpMany, createEntityResponse, } from 'gmp/commands/testing'; @@ -95,14 +94,13 @@ describe('PortListCommand tests', () => { action: 'delete_port_range', id: '12345', }); - const entityResponse = createEntityResponse('port_list', {id: '324'}); - const http = createHttpMany([response, entityResponse]); + const http = createHttp(response); const command = new PortListCommand(http); const result = await command.deletePortRange({ id: '12345', - portListId: '67890', }); - expect(result.data.id).toEqual('324'); + expect(result).toBeUndefined(); + expect(http.request).toHaveBeenCalledTimes(1); }); test('should allow to get a port list', async () => { diff --git a/src/gmp/commands/__tests__/web-application-target.test.ts b/src/gmp/commands/__tests__/web-application-target.test.ts index d68596e213..f59ac6a771 100644 --- a/src/gmp/commands/__tests__/web-application-target.test.ts +++ b/src/gmp/commands/__tests__/web-application-target.test.ts @@ -122,7 +122,7 @@ describe('WebApplicationTargetCommand tests', () => { in_use: YES_VALUE, }, }); - expect(result.data.id).toEqual('wat-123'); + expect(result).toBeUndefined(); }); test('should omit exclude_url when not provided in save', async () => { diff --git a/src/gmp/commands/credential-store.ts b/src/gmp/commands/credential-store.ts index a5611bfbee..588cfd5ee9 100644 --- a/src/gmp/commands/credential-store.ts +++ b/src/gmp/commands/credential-store.ts @@ -28,6 +28,10 @@ export interface CredentialStoreModifyParams { serverCaCertificate?: File; } +export interface CredentialStoreVerifyParams { + id: string; +} + const log = logger.getLogger('gmp.commands.credentialStore'); class CredentialStoreCommand extends EntityCommand< @@ -90,7 +94,7 @@ class CredentialStoreCommand extends EntityCommand< }); } - async verify({id}: {id: string}): Promise { + async verify({id}: CredentialStoreVerifyParams) { log.debug('Verifying credential store', {id}); return this.entityAction({ diff --git a/src/gmp/commands/credential.ts b/src/gmp/commands/credential.ts index c6aee885c9..3a89ff0f97 100644 --- a/src/gmp/commands/credential.ts +++ b/src/gmp/commands/credential.ts @@ -49,7 +49,7 @@ interface CredentialCommandSnmpFields { } // Create operation interfaces -type CredentialCommandCreateArgs = CredentialCommandBaseArgs; +export type CredentialCommandCreateParams = CredentialCommandBaseArgs; interface CredentialCommandKrb5Args extends CredentialCommandBaseArgs, CredentialCommandKrb5Fields {} @@ -145,9 +145,9 @@ class CredentialCommand extends EntityCommand< }; } - create(args: CredentialCommandCreateArgs) { + create(args: CredentialCommandCreateParams) { const baseData = this.createBase(args); - return this.action(baseData); + return this.entityAction(baseData); } createKrb5(args: CredentialCommandKrb5Args) { diff --git a/src/gmp/commands/port-list.ts b/src/gmp/commands/port-list.ts index 9a95d87312..64108e6fcb 100644 --- a/src/gmp/commands/port-list.ts +++ b/src/gmp/commands/port-list.ts @@ -26,19 +26,18 @@ export interface PortListCommandSaveParams { comment?: string; } -interface PortListCommandCreatePortRangeParams { +export interface PortListCommandCreatePortRangeParams { portListId: string; portRangeStart: number; portRangeEnd: number; portType: string; } -interface PortListCommandDeletePortRangeParams { +export interface PortListCommandDeletePortRangeParams { id: string; - portListId: string; } -interface PortListCommandImportParams { +export interface PortListCommandImportParams { xmlFile?: File; } @@ -101,16 +100,12 @@ class PortListCommand extends EntityCommand { }); } - async deletePortRange({ - id, - portListId, - }: PortListCommandDeletePortRangeParams) { - await this.httpPostWithTransform({ + async deletePortRange({id}: PortListCommandDeletePortRangeParams) { + await this.action({ cmd: 'delete_port_range', port_range_id: id, no_redirect: 1, }); - return await this.get({id: portListId}); } import({xmlFile}: PortListCommandImportParams) { diff --git a/src/gmp/commands/target.ts b/src/gmp/commands/target.ts index 1971f78f23..a0c5b53d06 100644 --- a/src/gmp/commands/target.ts +++ b/src/gmp/commands/target.ts @@ -16,7 +16,7 @@ import {UNSET_VALUE} from 'web/utils/Render'; export type TargetSource = 'manual' | 'file' | 'asset_hosts'; export type TargetExcludeSource = 'manual' | 'file'; -interface TargetCommandCreateParams { +export interface TargetCommandCreateParams { aliveTests?: AliveTest[]; allowSimultaneousIPs?: boolean; comment?: string; diff --git a/src/gmp/commands/user.ts b/src/gmp/commands/user.ts index 0e60a145b8..61b28f39fe 100644 --- a/src/gmp/commands/user.ts +++ b/src/gmp/commands/user.ts @@ -108,9 +108,9 @@ interface SaveArguments extends CreateArguments { oldName: string; } -interface DeleteArguments { +export interface UserCommandDeleteParams { id: string; - inheritorId: string; + inheritorId?: string; } const log = logger.getLogger('gmp.commands.users'); @@ -367,7 +367,7 @@ class UserCommand extends EntityCommand { return this.action(data); } - async delete({id, inheritorId}: DeleteArguments) { + async delete({id, inheritorId}: UserCommandDeleteParams) { const data = { cmd: 'delete_user', id, diff --git a/src/gmp/commands/web-application-target.ts b/src/gmp/commands/web-application-target.ts index 42c0f6b5ad..ef35048402 100644 --- a/src/gmp/commands/web-application-target.ts +++ b/src/gmp/commands/web-application-target.ts @@ -81,7 +81,7 @@ class WebApplicationTargetCommand extends EntityCommand { reverseLookupOnly, reverseLookupUnify, inUse, - }: WebApplicationTargetSaveParams): Promise { + }: WebApplicationTargetSaveParams) { log.debug('Modifying web application target', { id, name, @@ -89,7 +89,7 @@ class WebApplicationTargetCommand extends EntityCommand { urls, credentialId, }); - return this.entityAction({ + await this.entityAction({ cmd: 'save_web_application_target', web_application_target_id: id, name, diff --git a/src/web/entity/navigation.ts b/src/web/entity/navigation.ts index 86e5b39dd8..93c4ab1cd6 100644 --- a/src/web/entity/navigation.ts +++ b/src/web/entity/navigation.ts @@ -7,21 +7,23 @@ import {type EntityType} from 'gmp/utils/entity-type'; import {isDefined, isFunction} from 'gmp/utils/identity'; import {entityListURL, entityURL, type EntityListType} from 'web/route-paths'; -export type NavigateFunc = (path: string) => void; +type NavigateFunc = (path: string) => void; -export interface NavigateObj { +interface NavigateObj { navigate: NavigateFunc; } -export type Navigate = NavigateFunc | NavigateObj; +type Navigate = NavigateFunc | NavigateObj; -export interface GotoDetailsObj { - data: { - id: string; - }; +interface GotoDetailsData { + id: string; } -export type GotoDetailsFunc = ({data}: GotoDetailsObj) => void; +interface GotoDetailsObj { + data: GotoDetailsData; +} + +export type GotoDetailsFunc = (data: GotoDetailsObj | GotoDetailsData) => void; export type GotoListFunc = () => void; /** @@ -47,7 +49,8 @@ export const goToDetails = ( throw new Error('navigate function is required for goToDetails'); } } - return ({data}) => navigate(entityURL(type, String(data.id))); + return data => + navigate(entityURL(type, String('id' in data ? data.id : data.data.id))); }; /** diff --git a/src/web/hooks/use-query/__tests__/Agent.test.tsx b/src/web/hooks/use-query/__tests__/Agent.test.tsx new file mode 100644 index 0000000000..f291cb5346 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/Agent.test.tsx @@ -0,0 +1,97 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import {createSession} from 'gmp/testing'; +import { + useDeleteAgent, + useDownloadAgentSupportBundle, + useModifyAgent, +} from 'web/hooks/use-query/agent'; + +const createGmp = () => ({ + session: createSession({token: 'test-token'}), + settings: {}, + agent: { + save: testing.fn().mockResolvedValue(undefined), + delete: testing.fn().mockResolvedValue(undefined), + downloadSupportBundle: testing + .fn() + .mockResolvedValue({data: new ArrayBuffer(0)}), + }, +}); + +describe('useModifyAgent', () => { + test('should save an agent', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {agentsIds: ['agent-1'], authorized: true}; + const TestComponent = () => { + const mutation = useModifyAgent(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Save'})); + await waitFor(() => { + expect(gmp.agent.save.mock.calls[0][0]).toEqual(input); + }); + }); +}); + +describe('useDeleteAgent', () => { + test('should delete an agent', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useDeleteAgent({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + await waitFor(() => { + expect(gmp.agent.delete).toHaveBeenCalledWith({id: 'agent-1'}); + }); + }); +}); + +describe('useDownloadAgentSupportBundle', () => { + test('should download an agent support bundle', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useDownloadAgentSupportBundle(); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Download'})); + await waitFor(() => { + expect(gmp.agent.downloadSupportBundle).toHaveBeenCalledWith( + 'agent-1', + false, + ); + }); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/AgentGroup.test.tsx b/src/web/hooks/use-query/__tests__/AgentGroup.test.tsx new file mode 100644 index 0000000000..2e2b19f0bb --- /dev/null +++ b/src/web/hooks/use-query/__tests__/AgentGroup.test.tsx @@ -0,0 +1,105 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import {createSession} from 'gmp/testing'; +import { + useCloneAgentGroup, + useCreateAgentGroup, + useDeleteAgentGroup, + useSaveAgentGroup, +} from 'web/hooks/use-query/agent-group'; + +const createGmp = () => ({ + session: createSession({token: 'test-token'}), + settings: {}, + agentgroup: { + create: testing.fn().mockResolvedValue({data: {id: 'group-2'}}), + clone: testing.fn().mockResolvedValue({data: {id: 'group-2'}}), + save: testing.fn().mockResolvedValue(undefined), + delete: testing.fn().mockResolvedValue(undefined), + }, +}); + +describe('useCreateAgentGroup', () => { + test('should create an agent group', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {name: 'New Group'}; + + const TestComponent = () => { + const mutation = useCreateAgentGroup({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + + await waitFor(() => { + expect(gmp.agentgroup.create.mock.calls[0][0]).toEqual(input); + }); + }); +}); + +describe('useCloneAgentGroup', () => { + test('should clone an agent group', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {id: 'group-1', name: 'Cloned Group'}; + + const TestComponent = () => { + const mutation = useCloneAgentGroup({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + + await waitFor(() => { + expect(gmp.agentgroup.clone).toHaveBeenCalledWith({id: 'group-1'}); + }); + }); +}); + +describe('useSaveAgentGroup', () => { + test('should save an agent group', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {id: 'group-1', name: 'Saved Group'}; + + const TestComponent = () => { + const mutation = useSaveAgentGroup({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + + await waitFor(() => { + expect(gmp.agentgroup.save.mock.calls[0][0]).toEqual(input); + }); + }); +}); + +describe('useDeleteAgentGroup', () => { + test('should delete an agent group', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {id: 'group-1', name: 'Deleted Group'}; + + const TestComponent = () => { + const mutation = useDeleteAgentGroup({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + + await waitFor(() => { + expect(gmp.agentgroup.delete).toHaveBeenCalledWith({id: 'group-1'}); + }); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/AgentGroups.test.tsx b/src/web/hooks/use-query/__tests__/AgentGroups.test.tsx index 678f86c716..4e39b13d4d 100644 --- a/src/web/hooks/use-query/__tests__/AgentGroups.test.tsx +++ b/src/web/hooks/use-query/__tests__/AgentGroups.test.tsx @@ -4,17 +4,11 @@ */ import {describe, expect, test, testing} from '@gsa/testing'; -import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import {rendererWith, screen, waitFor} from 'web/testing'; import CollectionCounts from 'gmp/collection/collection-counts'; import QueryFilter from 'gmp/models/filter/query-filter'; import {createSession} from 'gmp/testing'; -import { - useCloneAgentGroup, - useCreateAgentGroup, - useDeleteAgentGroup, - useGetAgentGroups, - useSaveAgentGroup, -} from 'web/hooks/use-query/agent-groups'; +import {useGetAgentGroups} from 'web/hooks/use-query/agent-groups'; const filter = QueryFilter.fromString('name~group'); const group = {id: 'group-1', name: 'Agent Group 1'}; @@ -31,15 +25,9 @@ const createGmp = () => ({ }, }), }, - agentgroup: { - create: testing.fn().mockResolvedValue({data: {id: 'group-2'}}), - clone: testing.fn().mockResolvedValue({data: {id: 'group-2'}}), - save: testing.fn().mockResolvedValue(undefined), - delete: testing.fn().mockResolvedValue(undefined), - }, }); -describe('agent group query hooks', () => { +describe('useGetAgentGroups', () => { test('should fetch agent groups with a filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); @@ -57,28 +45,4 @@ describe('agent group query hooks', () => { expect(gmp.agentgroups.get).toHaveBeenCalledWith({filter}); }); - - test.each([ - ['create', useCreateAgentGroup, 'create', {name: 'New Group'}], - ['clone', useCloneAgentGroup, 'clone', {id: 'group-1'}], - ['save', useSaveAgentGroup, 'save', {id: 'group-1', name: 'Saved Group'}], - ['delete', useDeleteAgentGroup, 'delete', {id: 'group-1'}], - ])('should %s an agent group', async (_name, useMutation, method, input) => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useMutation({}); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Run'})); - - await waitFor(() => { - expect(gmp.agentgroup[method].mock.calls[0][0]).toEqual(input); - }); - }); }); diff --git a/src/web/hooks/use-query/__tests__/Agents.test.tsx b/src/web/hooks/use-query/__tests__/Agents.test.tsx index 36d265dfc3..f58c8366a6 100644 --- a/src/web/hooks/use-query/__tests__/Agents.test.tsx +++ b/src/web/hooks/use-query/__tests__/Agents.test.tsx @@ -14,10 +14,7 @@ import { useBulkDisableUpdateToLatestAgents, useBulkEnableUpdateToLatestAgents, useBulkRevokeAgents, - useDeleteAgent, - useDownloadAgentSupportBundle, useGetAgents, - useModifyAgent, useSyncAgents, } from 'web/hooks/use-query/agents'; @@ -96,165 +93,145 @@ describe('useGetAgents', () => { }); }); -describe('agent mutation hooks', () => { - test('should save an agent', async () => { +describe('useBulkDeleteAgents', () => { + test('should delete agents by entity list and filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); const TestComponent = () => { - const mutation = useModifyAgent(); + const mutation = useBulkDeleteAgents({}); return ( - + <> + + + ); }; render(); - fireEvent.click(screen.getByRole('button', {name: 'Save'})); + fireEvent.click(screen.getByRole('button', {name: 'Delete agents'})); + fireEvent.click(screen.getByRole('button', {name: 'Delete filter'})); await waitFor(() => { - expect(gmp.agent.save.mock.calls[0][0]).toEqual({ - agentsIds: ['agent-1'], - authorized: true, - }); + expect(gmp.agents.delete).toHaveBeenCalledWith([agent]); + expect(gmp.agents.deleteByFilter).toHaveBeenCalledWith(filter); }); }); +}); - test('should delete an agent', async () => { +describe('useBulkAuthorizeAgents', () => { + test('should authorize agents by entity list and filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - const TestComponent = () => { - const mutation = useDeleteAgent({}); + const mutation = useBulkAuthorizeAgents({}); return ( - + <> + + + ); }; - render(); - fireEvent.click(screen.getByRole('button', {name: 'Delete'})); - + fireEvent.click(screen.getByRole('button', {name: 'Entities'})); + fireEvent.click(screen.getByRole('button', {name: 'Filter'})); await waitFor(() => { - expect(gmp.agent.delete).toHaveBeenCalledWith({id: 'agent-1'}); + expect(gmp.agents.authorize).toHaveBeenCalledWith([agent]); + expect(gmp.agents.authorizeByFilter).toHaveBeenCalledWith(filter); }); }); +}); - test('should delete agents by entity list and filter', async () => { +describe('useBulkRevokeAgents', () => { + test('should revoke agents by entity list and filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - const TestComponent = () => { - const mutation = useBulkDeleteAgents({}); + const mutation = useBulkRevokeAgents({}); return ( <> - - + + ); }; - render(); - fireEvent.click(screen.getByRole('button', {name: 'Delete agents'})); - fireEvent.click(screen.getByRole('button', {name: 'Delete filter'})); - + fireEvent.click(screen.getByRole('button', {name: 'Entities'})); + fireEvent.click(screen.getByRole('button', {name: 'Filter'})); await waitFor(() => { - expect(gmp.agents.delete).toHaveBeenCalledWith([agent]); - expect(gmp.agents.deleteByFilter).toHaveBeenCalledWith(filter); + expect(gmp.agents.revoke).toHaveBeenCalledWith([agent]); + expect(gmp.agents.revokeByFilter).toHaveBeenCalledWith(filter); }); }); +}); - test.each([ - ['authorize', useBulkAuthorizeAgents, 'authorize', 'authorizeByFilter'], - ['revoke', useBulkRevokeAgents, 'revoke', 'revokeByFilter'], - [ - 'enable update to latest', - useBulkEnableUpdateToLatestAgents, - 'enableUpdateToLatest', - 'enableUpdateToLatestByFilter', - ], - [ - 'disable update to latest', - useBulkDisableUpdateToLatestAgents, - 'disableUpdateToLatest', - 'disableUpdateToLatestByFilter', - ], - ])( - 'should %s agents by entity list and filter', - async (_name, useMutation, entityMethod, filterMethod) => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useMutation({}); - return ( - <> - - - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Entities'})); - fireEvent.click(screen.getByRole('button', {name: 'Filter'})); - - await waitFor(() => { - expect(gmp.agents[entityMethod]).toHaveBeenCalledWith([agent]); - expect(gmp.agents[filterMethod]).toHaveBeenCalledWith(filter); - }); - }, - ); - - test('should sync agents', async () => { +describe('useBulkEnableUpdateToLatestAgents', () => { + test('should enable automatic updates by entity list and filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - const TestComponent = () => { - const mutation = useSyncAgents(); - return ; + const mutation = useBulkEnableUpdateToLatestAgents({}); + return ( + <> + + + + ); }; - render(); - fireEvent.click(screen.getByRole('button', {name: 'Sync'})); - + fireEvent.click(screen.getByRole('button', {name: 'Entities'})); + fireEvent.click(screen.getByRole('button', {name: 'Filter'})); await waitFor(() => { - expect(gmp.agents.sync).toHaveBeenCalledWith(); + expect(gmp.agents.enableUpdateToLatest).toHaveBeenCalledWith([agent]); + expect(gmp.agents.enableUpdateToLatestByFilter).toHaveBeenCalledWith( + filter, + ); }); }); +}); - test('should download an agent support bundle', async () => { +describe('useBulkDisableUpdateToLatestAgents', () => { + test('should disable automatic updates by entity list and filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - const TestComponent = () => { - const mutation = useDownloadAgentSupportBundle(); + const mutation = useBulkDisableUpdateToLatestAgents({}); return ( - + <> + + + ); }; + render(); + fireEvent.click(screen.getByRole('button', {name: 'Entities'})); + fireEvent.click(screen.getByRole('button', {name: 'Filter'})); + await waitFor(() => { + expect(gmp.agents.disableUpdateToLatest).toHaveBeenCalledWith([agent]); + expect(gmp.agents.disableUpdateToLatestByFilter).toHaveBeenCalledWith( + filter, + ); + }); + }); +}); + +describe('useSyncAgents', () => { + test('should sync agents', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useSyncAgents(); + return ; + }; render(); - fireEvent.click(screen.getByRole('button', {name: 'Download'})); + fireEvent.click(screen.getByRole('button', {name: 'Sync'})); await waitFor(() => { - expect(gmp.agent.downloadSupportBundle).toHaveBeenCalledWith( - 'agent-1', - false, - ); + expect(gmp.agents.sync).toHaveBeenCalledWith(); }); }); }); diff --git a/src/web/hooks/use-query/__tests__/Audit.test.tsx b/src/web/hooks/use-query/__tests__/Audit.test.tsx new file mode 100644 index 0000000000..d7a3cd6130 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/Audit.test.tsx @@ -0,0 +1,87 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, test, expect, testing} from '@gsa/testing'; +import {rendererWith, screen, waitFor} from 'web/testing'; +import Audit, {AUDIT_STATUS} from 'gmp/models/audit'; +import {createSession} from 'gmp/testing'; +import {useGetAudit} from 'web/hooks/use-query/audit'; + +const audit = Audit.fromElement({ + _id: 'audit-1', + name: 'Test Audit', + status: AUDIT_STATUS.done, + usage_type: 'audit', + permissions: {permission: [{name: 'everything'}]}, +}); + +const createGmp = ({token}: {token?: string} = {token: 'test-token'}) => ({ + audit: { + get: testing.fn().mockResolvedValue({data: audit}), + }, + session: createSession({token}), + settings: {}, +}); + +const SingleAuditComponent = ({id}: {id: string}) => { + const {data, isLoading, isError} = useGetAudit({id}); + + if (isLoading) return
Loading...
; + if (isError) return
Error
; + if (!data) return
No data
; + + return ( +
+ {data.name} + {data.id} +
+ ); +}; + +describe('useGetAudit', () => { + test('should fetch a single audit', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + render(); + + await waitFor(() => { + expect(screen.getByTestId('audit-name')).toHaveTextContent('Test Audit'); + }); + expect(gmp.audit.get).toHaveBeenCalledWith({id: 'audit-1'}); + expect(screen.getByTestId('audit-id')).toHaveTextContent('audit-1'); + }); + + test('should show loading state initially', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + render(); + expect(screen.getByTestId('loading')).toBeInTheDocument(); + }); + + test('should not fetch an audit when the ID is empty', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + render(); + expect(screen.getByTestId('no-data')).toBeInTheDocument(); + expect(gmp.audit.get).not.toHaveBeenCalled(); + }); + + test('should not fetch an audit without a session token', () => { + const gmp = createGmp({token: undefined}); + const {render} = rendererWith({gmp, router: true}); + render(); + expect(gmp.audit.get).not.toHaveBeenCalled(); + }); + + test('should show an error when fetching an audit fails', async () => { + const gmp = createGmp(); + gmp.audit.get.mockRejectedValue(new Error('Request failed')); + const {render} = rendererWith({gmp, router: true}); + render(); + await waitFor(() => { + expect(screen.getByTestId('error')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/Audits.test.tsx b/src/web/hooks/use-query/__tests__/Audits.test.tsx index 0b1d5c0615..2cb1556731 100644 --- a/src/web/hooks/use-query/__tests__/Audits.test.tsx +++ b/src/web/hooks/use-query/__tests__/Audits.test.tsx @@ -10,7 +10,7 @@ import Audit, {AUDIT_STATUS} from 'gmp/models/audit'; import type FilterType from 'gmp/models/filter/filter-type'; import QueryFilter from 'gmp/models/filter/query-filter'; import {createSession} from 'gmp/testing'; -import {useGetAudit, useGetAudits} from 'web/hooks/use-query/audits'; +import {useGetAudits} from 'web/hooks/use-query/audits'; const audit = Audit.fromElement({ _id: 'audit-1', @@ -30,27 +30,6 @@ const audit2 = Audit.fromElement({ const filter = QueryFilter.fromString('name~test'); -const SingleAuditComponent = ({id}: {id: string}) => { - const {data, isLoading, isError} = useGetAudit({id}); - - if (isLoading) { - return
Loading...
; - } - if (isError) { - return
Error
; - } - if (!data) { - return
No data
; - } - - return ( -
- {data.name} - {data.id} -
- ); -}; - const AuditListComponent = ({filter}: {filter?: FilterType}) => { const {data, isLoading, isError} = useGetAudits( filter ? {filter} : undefined, @@ -95,60 +74,6 @@ const createGmp = ({token}: {token?: string} = {token: 'test-token'}) => ({ settings: {}, }); -describe('useGetAudit', () => { - test('should fetch a single audit', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - render(); - - await waitFor(() => { - expect(screen.getByTestId('audit-name')).toHaveTextContent('Test Audit'); - }); - - expect(gmp.audit.get).toHaveBeenCalledWith({id: 'audit-1'}); - expect(screen.getByTestId('audit-id')).toHaveTextContent('audit-1'); - }); - - test('should show loading state initially', () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - render(); - - expect(screen.getByTestId('loading')).toBeInTheDocument(); - }); - - test('should not fetch an audit when the ID is empty', () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - render(); - - expect(screen.getByTestId('no-data')).toBeInTheDocument(); - expect(gmp.audit.get).not.toHaveBeenCalled(); - }); - - test('should not fetch an audit without a session token', () => { - const gmp = createGmp({token: undefined}); - const {render} = rendererWith({gmp, router: true}); - - render(); - - expect(gmp.audit.get).not.toHaveBeenCalled(); - }); - - test('should show an error when fetching an audit fails', async () => { - const gmp = createGmp(); - gmp.audit.get.mockRejectedValue(new Error('Request failed')); - const {render} = rendererWith({gmp, router: true}); - - render(); - - await waitFor(() => { - expect(screen.getByTestId('error')).toBeInTheDocument(); - }); - }); -}); - describe('useGetAudits', () => { test('should fetch a list of audits', async () => { const gmp = createGmp(); diff --git a/src/web/hooks/use-query/__tests__/Credential.test.tsx b/src/web/hooks/use-query/__tests__/Credential.test.tsx new file mode 100644 index 0000000000..0972df9c91 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/Credential.test.tsx @@ -0,0 +1,94 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import {type CredentialCommandCreateParams} from 'gmp/commands/credential'; +import {USERNAME_PASSWORD_CREDENTIAL_TYPE} from 'gmp/models/credential'; +import {createSession} from 'gmp/testing'; +import {useCreateCredential} from 'web/hooks/use-query/credential'; + +const createGmp = () => ({ + session: createSession({token: 'test-token'}), + settings: {}, + credential: { + create: testing.fn().mockResolvedValue({data: {id: 'credential-1'}}), + }, +}); + +describe('useCreateCredential', () => { + test('should create a credential', async () => { + const gmp = createGmp(); + const input: CredentialCommandCreateParams = { + name: 'Credential 1', + credentialType: USERNAME_PASSWORD_CREDENTIAL_TYPE, + credentialLogin: 'admin', + }; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreateCredential(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => { + expect(gmp.credential.create).toHaveBeenCalledWith(input); + }); + }); + + test('should call onSuccess with the created credential data', async () => { + const gmp = createGmp(); + const onSuccess = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreateCredential({onSuccess}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => { + expect(onSuccess).toHaveBeenCalledWith({id: 'credential-1'}); + }); + }); + + test('should call onError when creating a credential fails', async () => { + const error = new Error('Create failed'); + const gmp = createGmp(); + gmp.credential.create.mockRejectedValue(error); + const onError = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreateCredential({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/CredentialStore.test.tsx b/src/web/hooks/use-query/__tests__/CredentialStore.test.tsx index 3524029504..6fe086d75d 100644 --- a/src/web/hooks/use-query/__tests__/CredentialStore.test.tsx +++ b/src/web/hooks/use-query/__tests__/CredentialStore.test.tsx @@ -5,81 +5,53 @@ import {describe, expect, test, testing} from '@gsa/testing'; import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; -import CollectionCounts from 'gmp/collection/collection-counts'; -import QueryFilter from 'gmp/models/filter/query-filter'; import {createSession} from 'gmp/testing'; import { useEditCredentialStore, - useGetCredentialStores, useVerifyCredentialStore, } from 'web/hooks/use-query/credential-store'; -const filter = QueryFilter.fromString('name~credential'); - const createGmp = () => ({ session: createSession({token: 'test-token'}), settings: {}, - credentialstores: { - get: testing.fn().mockResolvedValue({ - data: [{id: 'credential-1', name: 'Credential 1'}], - meta: { - filter, - counts: new CollectionCounts({all: 1, filtered: 1, length: 1}), - }, - }), - }, credentialstore: { edit: testing.fn().mockResolvedValue({data: {id: 'credential-1'}}), verify: testing.fn().mockResolvedValue({data: {id: 'credential-1'}}), }, }); -describe('credential store query hooks', () => { - test('should fetch credential stores with a filter', async () => { +describe('useEditCredentialStore', () => { + test('should edit a credential store', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - + const input = {id: 'credential-1', active: true}; const TestComponent = () => { - const {data} = useGetCredentialStores({filter}); - return
{data?.entities[0]?.name}
; + const mutation = useEditCredentialStore({}); + return ; }; render(); - + fireEvent.click(screen.getByRole('button', {name: 'Run'})); await waitFor(() => { - expect(screen.getByTestId('credential')).toHaveTextContent( - 'Credential 1', - ); + expect(gmp.credentialstore.edit).toHaveBeenCalledWith(input); }); - - expect(gmp.credentialstores.get).toHaveBeenCalledWith({filter}); }); +}); - test.each([ - [ - 'edit', - useEditCredentialStore, - 'edit', - {id: 'credential-1', active: true}, - ], - ['verify', useVerifyCredentialStore, 'verify', {id: 'credential-1'}], - ])( - 'should %s a credential store', - async (_name, useMutation, method, input) => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useMutation({}); - return ; - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Run'})); +describe('useVerifyCredentialStore', () => { + test('should verify a credential store', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {id: 'credential-1'}; + const TestComponent = () => { + const mutation = useVerifyCredentialStore({}); + return ; + }; - await waitFor(() => { - expect(gmp.credentialstore[method].mock.calls[0][0]).toEqual(input); - }); - }, - ); + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + await waitFor(() => { + expect(gmp.credentialstore.verify).toHaveBeenCalledWith(input); + }); + }); }); diff --git a/src/web/hooks/use-query/__tests__/CredentialStores.test.tsx b/src/web/hooks/use-query/__tests__/CredentialStores.test.tsx new file mode 100644 index 0000000000..a7a70ffd82 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/CredentialStores.test.tsx @@ -0,0 +1,46 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {rendererWith, screen, waitFor} from 'web/testing'; +import CollectionCounts from 'gmp/collection/collection-counts'; +import QueryFilter from 'gmp/models/filter/query-filter'; +import {createSession} from 'gmp/testing'; +import {useGetCredentialStores} from 'web/hooks/use-query/credential-stores'; + +const filter = QueryFilter.fromString('name~credential'); + +const createGmp = () => ({ + session: createSession({token: 'test-token'}), + settings: {}, + credentialstores: { + get: testing.fn().mockResolvedValue({ + data: [{id: 'credential-1', name: 'Credential 1'}], + meta: { + filter, + counts: new CollectionCounts({all: 1, filtered: 1, length: 1}), + }, + }), + }, +}); + +describe('useGetCredentialStores', () => { + test('should fetch credential stores with a filter', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const {data} = useGetCredentialStores({filter}); + return
{data?.entities[0]?.name}
; + }; + + render(); + await waitFor(() => { + expect(screen.getByTestId('credential')).toHaveTextContent( + 'Credential 1', + ); + }); + expect(gmp.credentialstores.get).toHaveBeenCalledWith({filter}); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/Credentials.test.tsx b/src/web/hooks/use-query/__tests__/Credentials.test.tsx new file mode 100644 index 0000000000..c4f43af5e6 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/Credentials.test.tsx @@ -0,0 +1,149 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {rendererWith, screen, waitFor} from 'web/testing'; +import CollectionCounts from 'gmp/collection/collection-counts'; +import Credential, { + USERNAME_PASSWORD_CREDENTIAL_TYPE, +} from 'gmp/models/credential'; +import QueryFilter from 'gmp/models/filter/query-filter'; +import {createSession} from 'gmp/testing'; +import { + useGetAllCredentials, + useGetCredentials, +} from 'web/hooks/use-query/credentials'; + +const filter = QueryFilter.fromString('name~credential'); +const credentials = [ + new Credential({ + id: 'credential-1', + name: 'Credential 1', + credentialType: USERNAME_PASSWORD_CREDENTIAL_TYPE, + }), +]; + +const createGmp = () => { + const response = { + data: credentials, + meta: { + filter, + counts: new CollectionCounts({all: 1, filtered: 1, length: 1}), + }, + }; + + return { + session: createSession({token: 'test-token'}), + settings: {}, + credentials: { + get: testing.fn().mockResolvedValue(response), + getAll: testing.fn().mockResolvedValue(response), + }, + }; +}; + +describe('useGetCredentials', () => { + test('should fetch credentials with a filter', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const {data} = useGetCredentials({filter}); + return
{data?.entities[0]?.name}
; + }; + + render(); + + await waitFor(() => { + expect(screen.getByTestId('credential')).toHaveTextContent( + 'Credential 1', + ); + }); + + expect(gmp.credentials.get).toHaveBeenCalledWith({filter}); + expect(gmp.credentials.getAll).not.toHaveBeenCalled(); + }); + + test('should not fetch credentials when disabled', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetCredentials({enabled: false}); + return
; + }; + + render(); + + expect(gmp.credentials.get).not.toHaveBeenCalled(); + }); + + test('should not fetch credentials without a session token', () => { + const gmp = createGmp(); + gmp.session.token = undefined; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetCredentials({filter}); + return
; + }; + + render(); + + expect(gmp.credentials.get).not.toHaveBeenCalled(); + }); +}); + +describe('useGetAllCredentials', () => { + test('should fetch all credentials with a filter', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const {data} = useGetAllCredentials({filter}); + return
{data?.entities[0]?.name}
; + }; + + render(); + + await waitFor(() => { + expect(screen.getByTestId('credential')).toHaveTextContent( + 'Credential 1', + ); + }); + + expect(gmp.credentials.getAll).toHaveBeenCalledWith({filter}); + expect(gmp.credentials.get).not.toHaveBeenCalled(); + }); + + test('should not fetch credentials when disabled', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetAllCredentials({enabled: false}); + return
; + }; + + render(); + + expect(gmp.credentials.getAll).not.toHaveBeenCalled(); + }); + + test('should not fetch credentials without a session token', () => { + const gmp = createGmp(); + gmp.session.token = undefined; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetAllCredentials({filter}); + return
; + }; + + render(); + + expect(gmp.credentials.getAll).not.toHaveBeenCalled(); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/Groups.test.tsx b/src/web/hooks/use-query/__tests__/Groups.test.tsx index 7b4f839651..a4c576d023 100644 --- a/src/web/hooks/use-query/__tests__/Groups.test.tsx +++ b/src/web/hooks/use-query/__tests__/Groups.test.tsx @@ -7,11 +7,12 @@ import {describe, expect, test, testing} from '@gsa/testing'; import {rendererWith, screen, waitFor} from 'web/testing'; import CollectionCounts from 'gmp/collection/collection-counts'; import QueryFilter from 'gmp/models/filter/query-filter'; +import Group from 'gmp/models/group'; import {createSession} from 'gmp/testing'; import {useGetAllGroups, useGetGroups} from 'web/hooks/use-query/groups'; const filter = QueryFilter.fromString('name~group'); -const groups = [{id: 'group-1', name: 'Group 1'}]; +const groups = [new Group({id: 'group-1', name: 'Group 1'})]; const createGmp = () => { const response = { @@ -32,7 +33,7 @@ const createGmp = () => { }; }; -describe('group query hooks', () => { +describe('useGetGroups', () => { test('should fetch groups with a filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); @@ -52,6 +53,37 @@ describe('group query hooks', () => { expect(gmp.groups.getAll).not.toHaveBeenCalled(); }); + test('should not fetch groups when disabled', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetGroups({enabled: false}); + return
; + }; + + render(); + + expect(gmp.groups.get).not.toHaveBeenCalled(); + }); + + test('should not fetch groups without a session token', () => { + const gmp = createGmp(); + gmp.session.token = undefined; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetGroups({filter}); + return
; + }; + + render(); + + expect(gmp.groups.get).not.toHaveBeenCalled(); + }); +}); + +describe('useGetAllGroups', () => { test('should fetch all groups with a filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); @@ -76,14 +108,12 @@ describe('group query hooks', () => { const {render} = rendererWith({gmp, router: true}); const TestComponent = () => { - useGetGroups({enabled: false}); useGetAllGroups({enabled: false}); return
; }; render(); - expect(gmp.groups.get).not.toHaveBeenCalled(); expect(gmp.groups.getAll).not.toHaveBeenCalled(); }); @@ -93,14 +123,12 @@ describe('group query hooks', () => { const {render} = rendererWith({gmp, router: true}); const TestComponent = () => { - useGetGroups({filter}); useGetAllGroups({filter}); return
; }; render(); - expect(gmp.groups.get).not.toHaveBeenCalled(); expect(gmp.groups.getAll).not.toHaveBeenCalled(); }); }); diff --git a/src/web/hooks/use-query/__tests__/OciImageTarget.test.tsx b/src/web/hooks/use-query/__tests__/OciImageTarget.test.tsx new file mode 100644 index 0000000000..2cee468c76 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/OciImageTarget.test.tsx @@ -0,0 +1,115 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import {createSession} from 'gmp/testing'; +import { + useCloneOciImageTarget, + useCreateOciImageTarget, + useDeleteOciImageTarget, + useSaveOciImageTarget, +} from 'web/hooks/use-query/oci-image-target'; + +const target = {id: 'target-1', name: 'Target 1'}; + +const createGmp = () => ({ + session: createSession({token: 'test-token'}), + settings: {}, + ociimagetarget: { + create: testing.fn().mockResolvedValue({data: {id: 'target-2'}}), + save: testing.fn().mockResolvedValue({data: {id: 'target-1'}}), + delete: testing.fn().mockResolvedValue(undefined), + clone: testing.fn().mockResolvedValue({data: {id: 'target-2'}}), + }, +}); + +describe('useCreateOciImageTarget', () => { + test('should create an OCI image target', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {name: 'New Target', imageReferences: 'registry/image'}; + + const TestComponent = () => { + const mutation = useCreateOciImageTarget({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => { + expect(gmp.ociimagetarget.create.mock.calls[0][0]).toEqual(input); + }); + }); +}); + +describe('useSaveOciImageTarget', () => { + test('should save an OCI image target', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {id: target.id, name: 'Saved'}; + + const TestComponent = () => { + const mutation = useSaveOciImageTarget({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Save'})); + + await waitFor(() => { + expect(gmp.ociimagetarget.save.mock.calls[0][0]).toEqual(input); + }); + }); +}); + +describe('useDeleteOciImageTarget', () => { + test('should delete an OCI image target', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useDeleteOciImageTarget({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => { + expect(gmp.ociimagetarget.delete).toHaveBeenCalledWith({id: target.id}); + }); + }); +}); + +describe('useCloneOciImageTarget', () => { + test('should clone an OCI image target', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCloneOciImageTarget({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Clone'})); + + await waitFor(() => { + expect(gmp.ociimagetarget.clone).toHaveBeenCalledWith({id: target.id}); + }); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/OciImageTargets.test.tsx b/src/web/hooks/use-query/__tests__/OciImageTargets.test.tsx index 315e4de444..228a472104 100644 --- a/src/web/hooks/use-query/__tests__/OciImageTargets.test.tsx +++ b/src/web/hooks/use-query/__tests__/OciImageTargets.test.tsx @@ -11,11 +11,7 @@ import {createSession} from 'gmp/testing'; import { useBulkDeleteOciImageTargets, useBulkExportOciImageTargets, - useCloneOciImageTarget, - useCreateOciImageTarget, - useDeleteOciImageTarget, useGetOciImageTargets, - useSaveOciImageTarget, } from 'web/hooks/use-query/oci-image-targets'; const filter = QueryFilter.fromString('name~target'); @@ -37,15 +33,9 @@ const createGmp = () => ({ export: testing.fn().mockResolvedValue({data: 'targets'}), exportByFilter: testing.fn().mockResolvedValue({data: 'targets'}), }, - ociimagetarget: { - create: testing.fn().mockResolvedValue({data: {id: 'target-2'}}), - save: testing.fn().mockResolvedValue({data: {id: 'target-1'}}), - delete: testing.fn().mockResolvedValue(undefined), - clone: testing.fn().mockResolvedValue({data: {id: 'target-2'}}), - }, }); -describe('OCI image target query hooks', () => { +describe('useGetOciImageTargets', () => { test('should fetch OCI image targets with a filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); @@ -63,56 +53,47 @@ describe('OCI image target query hooks', () => { expect(gmp.ociimagetargets.get).toHaveBeenCalledWith({filter}); }); +}); - test('should forward OCI image target mutations', async () => { +describe('useBulkDeleteOciImageTargets', () => { + test('should delete OCI image targets', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - const input = {name: 'New Target', imageReferences: 'registry/image'}; const TestComponent = () => { - const create = useCreateOciImageTarget({}); - const save = useSaveOciImageTarget({}); - const remove = useDeleteOciImageTarget({}); - const clone = useCloneOciImageTarget({}); - const bulkDelete = useBulkDeleteOciImageTargets({}); - const bulkExport = useBulkExportOciImageTargets({}); + const mutation = useBulkDeleteOciImageTargets({}); return ( - <> - - - - - - - + ); }; render(); - fireEvent.click(screen.getByRole('button', {name: 'Create'})); - fireEvent.click(screen.getByRole('button', {name: 'Save'})); fireEvent.click(screen.getByRole('button', {name: 'Delete'})); - fireEvent.click(screen.getByRole('button', {name: 'Clone'})); - fireEvent.click(screen.getByRole('button', {name: 'Bulk delete'})); - fireEvent.click(screen.getByRole('button', {name: 'Bulk export'})); await waitFor(() => { - expect(gmp.ociimagetarget.create.mock.calls[0][0]).toEqual(input); - expect(gmp.ociimagetarget.save.mock.calls[0][0]).toEqual({ - id: 'target-1', - name: 'Saved', - }); - expect(gmp.ociimagetarget.delete).toHaveBeenCalledWith({id: 'target-1'}); - expect(gmp.ociimagetarget.clone).toHaveBeenCalledWith({id: 'target-1'}); expect(gmp.ociimagetargets.delete).toHaveBeenCalledWith([target]); + }); + }); +}); + +describe('useBulkExportOciImageTargets', () => { + test('should export OCI image targets by filter', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useBulkExportOciImageTargets({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Export'})); + + await waitFor(() => { expect(gmp.ociimagetargets.exportByFilter).toHaveBeenCalledWith(filter); }); }); diff --git a/src/web/hooks/use-query/__tests__/Policies.test.tsx b/src/web/hooks/use-query/__tests__/Policies.test.tsx index 1824575b71..0b0f960071 100644 --- a/src/web/hooks/use-query/__tests__/Policies.test.tsx +++ b/src/web/hooks/use-query/__tests__/Policies.test.tsx @@ -10,7 +10,7 @@ import type FilterType from 'gmp/models/filter/filter-type'; import QueryFilter from 'gmp/models/filter/query-filter'; import Policy from 'gmp/models/policy'; import {createSession} from 'gmp/testing'; -import {useGetPolicy, useGetPolicies} from 'web/hooks/use-query/policies'; +import {useGetPolicies} from 'web/hooks/use-query/policies'; const policy = Policy.fromElement({ _id: 'policy-1', @@ -32,27 +32,6 @@ const policy2 = Policy.fromElement({ const filter = QueryFilter.fromString('name~test'); -const SinglePolicyComponent = ({id}: {id: string}) => { - const {data, isLoading, isError} = useGetPolicy({id}); - - if (isLoading) { - return
Loading...
; - } - if (isError) { - return
Error
; - } - if (!data) { - return
No data
; - } - - return ( -
- {data.name} - {data.id} -
- ); -}; - const PolicyListComponent = ({filter}: {filter?: FilterType}) => { const {data, isLoading, isError} = useGetPolicies( filter ? {filter} : undefined, @@ -82,9 +61,6 @@ const PolicyListComponent = ({filter}: {filter?: FilterType}) => { const createGmp = ({token}: {token?: string} = {token: 'test-token'}) => ({ session: createSession({token}), settings: {}, - policy: { - get: testing.fn().mockResolvedValue({data: policy}), - }, policies: { get: testing.fn().mockResolvedValue({ data: [policy, policy2], @@ -96,62 +72,6 @@ const createGmp = ({token}: {token?: string} = {token: 'test-token'}) => ({ }, }); -describe('useGetPolicy', () => { - test('should fetch a single policy', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - render(); - - await waitFor(() => { - expect(screen.getByTestId('policy-name')).toHaveTextContent( - 'Test Policy', - ); - }); - - expect(gmp.policy.get).toHaveBeenCalledWith({id: 'policy-1'}); - expect(screen.getByTestId('policy-id')).toHaveTextContent('policy-1'); - }); - - test('should show loading state initially', () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - render(); - - expect(screen.getByTestId('loading')).toBeInTheDocument(); - }); - - test('should not fetch a policy when the ID is empty', () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - render(); - - expect(screen.getByTestId('no-data')).toBeInTheDocument(); - expect(gmp.policy.get).not.toHaveBeenCalled(); - }); - - test('should not fetch a policy without a session token', () => { - const gmp = createGmp({token: undefined}); - const {render} = rendererWith({gmp, router: true}); - - render(); - - expect(gmp.policy.get).not.toHaveBeenCalled(); - }); - - test('should show an error when fetching a policy fails', async () => { - const gmp = createGmp(); - gmp.policy.get.mockRejectedValue(new Error('Request failed')); - const {render} = rendererWith({gmp, router: true}); - - render(); - - await waitFor(() => { - expect(screen.getByTestId('error')).toBeInTheDocument(); - }); - }); -}); - describe('useGetPolicies', () => { test('should fetch a list of policies', async () => { const gmp = createGmp(); diff --git a/src/web/hooks/use-query/__tests__/Policy.test.tsx b/src/web/hooks/use-query/__tests__/Policy.test.tsx new file mode 100644 index 0000000000..9bbda42e0d --- /dev/null +++ b/src/web/hooks/use-query/__tests__/Policy.test.tsx @@ -0,0 +1,104 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {rendererWith, screen, waitFor} from 'web/testing'; +import Policy from 'gmp/models/policy'; +import {createSession} from 'gmp/testing'; +import {useGetPolicy} from 'web/hooks/use-query/policy'; + +const policy = Policy.fromElement({ + _id: 'policy-1', + name: 'Test Policy', + comment: 'A test policy', + creation_time: '2019-07-16T06:31:29Z', + modification_time: '2019-07-16T06:44:55Z', + permissions: {permission: [{name: 'everything'}]}, +}); + +const SinglePolicyComponent = ({id}: {id: string}) => { + const {data, isLoading, isError} = useGetPolicy({id}); + + if (isLoading) { + return
Loading...
; + } + if (isError) { + return
Error
; + } + if (!data) { + return
No data
; + } + + return ( +
+ {data.name} + {data.id} +
+ ); +}; + +const createGmp = ({token}: {token?: string} = {token: 'test-token'}) => ({ + session: createSession({token}), + settings: {}, + policy: { + get: testing.fn().mockResolvedValue({data: policy}), + }, +}); + +describe('useGetPolicy', () => { + test('should fetch a single policy', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + render(); + + await waitFor(() => { + expect(screen.getByTestId('policy-name')).toHaveTextContent( + 'Test Policy', + ); + }); + + expect(gmp.policy.get).toHaveBeenCalledWith({id: 'policy-1'}); + expect(screen.getByTestId('policy-id')).toHaveTextContent('policy-1'); + }); + + test('should show loading state initially', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + render(); + + expect(screen.getByTestId('loading')).toBeInTheDocument(); + }); + + test('should not fetch a policy when the ID is empty', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + render(); + + expect(screen.getByTestId('no-data')).toBeInTheDocument(); + expect(gmp.policy.get).not.toHaveBeenCalled(); + }); + + test('should not fetch a policy without a session token', () => { + const gmp = createGmp({token: undefined}); + const {render} = rendererWith({gmp, router: true}); + + render(); + + expect(gmp.policy.get).not.toHaveBeenCalled(); + }); + + test('should show an error when fetching a policy fails', async () => { + const gmp = createGmp(); + gmp.policy.get.mockRejectedValue(new Error('Request failed')); + const {render} = rendererWith({gmp, router: true}); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('error')).toBeInTheDocument(); + }); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/PortList.test.tsx b/src/web/hooks/use-query/__tests__/PortList.test.tsx new file mode 100644 index 0000000000..9e2994449e --- /dev/null +++ b/src/web/hooks/use-query/__tests__/PortList.test.tsx @@ -0,0 +1,301 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import Response from 'gmp/http/response'; +import {createSession} from 'gmp/testing'; +import { + useClonePortList, + useCreatePortList, + useCreatePortRange, + useDeletePortList, + useDeletePortRange, + useImportPortList, + useSavePortList, +} from 'web/hooks/use-query/port-list'; + +const createGmp = () => ({ + session: createSession({token: 'test-token'}), + settings: {}, + portlist: { + create: testing.fn().mockResolvedValue(new Response({id: 'port-list-1'})), + save: testing.fn().mockResolvedValue(new Response({id: 'port-list-1'})), + clone: testing.fn().mockResolvedValue(new Response({id: 'port-list-2'})), + delete: testing.fn().mockResolvedValue(undefined), + import: testing.fn().mockResolvedValue(new Response({id: 'port-list-1'})), + createPortRange: testing + .fn() + .mockResolvedValue(new Response({id: 'port-range-1'})), + deletePortRange: testing.fn().mockResolvedValue(undefined), + }, +}); + +describe('useCreatePortList', () => { + test('should create a port list', async () => { + const gmp = createGmp(); + const input = { + name: 'Port List 1', + comment: 'Port list comment', + portRange: '1-1024', + }; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreatePortList(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => { + expect(gmp.portlist.create).toHaveBeenCalledWith(input); + }); + }); + + test('should call onSuccess with the created port list data', async () => { + const gmp = createGmp(); + const onSuccess = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreatePortList({onSuccess}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => { + expect(onSuccess).toHaveBeenCalledWith({id: 'port-list-1'}); + }); + }); + + test('should call onError when creating a port list fails', async () => { + const error = new Error('Create failed'); + const gmp = createGmp(); + gmp.portlist.create.mockRejectedValue(error); + const onError = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreatePortList({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); + +describe('useSavePortList', () => { + test('should save a port list', async () => { + const gmp = createGmp(); + const input = {id: 'port-list-1', name: 'Saved Port List'}; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useSavePortList(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Save'})); + + await waitFor(() => { + expect(gmp.portlist.save).toHaveBeenCalledWith(input); + }); + }); +}); + +describe('useClonePortList', () => { + test('should clone a port list by ID', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useClonePortList(); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Clone'})); + + await waitFor(() => { + expect(gmp.portlist.clone).toHaveBeenCalledWith({id: 'port-list-1'}); + }); + }); +}); + +describe('useDeletePortList', () => { + test('should delete a port list by ID', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useDeletePortList(); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => { + expect(gmp.portlist.delete).toHaveBeenCalledWith({ + id: 'port-list-1', + }); + }); + }); +}); + +describe('useImportPortList', () => { + test('should import a port list', async () => { + const gmp = createGmp(); + const input = {xmlFile: new File(['port list'], 'port-list.xml')}; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useImportPortList(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Import'})); + + await waitFor(() => { + expect(gmp.portlist.import).toHaveBeenCalledWith(input); + }); + }); + + test('should call onSuccess with the imported port list data', async () => { + const gmp = createGmp(); + const onSuccess = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useImportPortList({onSuccess}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Import'})); + + await waitFor(() => { + expect(onSuccess).toHaveBeenCalledWith({id: 'port-list-1'}); + }); + }); + + test('should call onError when importing a port list fails', async () => { + const error = new Error('Import failed'); + const gmp = createGmp(); + gmp.portlist.import.mockRejectedValue(error); + const onError = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useImportPortList({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Import'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); + +describe('useCreatePortRange', () => { + test('should create a port range', async () => { + const gmp = createGmp(); + const input = { + portListId: 'port-list-1', + portRangeStart: 1, + portRangeEnd: 1024, + portType: 'tcp', + }; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreatePortRange(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => { + expect(gmp.portlist.createPortRange).toHaveBeenCalledWith(input); + }); + }); +}); + +describe('useDeletePortRange', () => { + test('should delete a port range', async () => { + const gmp = createGmp(); + const input = {id: 'port-range-1', portListId: 'port-list-1'}; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useDeletePortRange(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => { + expect(gmp.portlist.deletePortRange).toHaveBeenCalledWith({ + id: 'port-range-1', + }); + }); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/PortLists.test.tsx b/src/web/hooks/use-query/__tests__/PortLists.test.tsx new file mode 100644 index 0000000000..31607284f6 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/PortLists.test.tsx @@ -0,0 +1,137 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {rendererWith, screen, waitFor} from 'web/testing'; +import CollectionCounts from 'gmp/collection/collection-counts'; +import QueryFilter from 'gmp/models/filter/query-filter'; +import PortList from 'gmp/models/port-list'; +import {createSession} from 'gmp/testing'; +import { + useGetAllPortLists, + useGetPortLists, +} from 'web/hooks/use-query/port-lists'; + +const filter = QueryFilter.fromString('name~port-list'); +const portLists = [new PortList({id: 'port-list-1', name: 'Port List 1'})]; + +const createGmp = () => { + const response = { + data: portLists, + meta: { + filter, + counts: new CollectionCounts({all: 1, filtered: 1, length: 1}), + }, + }; + + return { + session: createSession({token: 'test-token'}), + settings: {}, + portlists: { + get: testing.fn().mockResolvedValue(response), + getAll: testing.fn().mockResolvedValue(response), + }, + }; +}; + +describe('useGetPortLists', () => { + test('should fetch port lists with a filter', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const {data} = useGetPortLists({filter}); + return
{data?.entities[0]?.name}
; + }; + + render(); + + await waitFor(() => { + expect(screen.getByTestId('port-list')).toHaveTextContent('Port List 1'); + }); + + expect(gmp.portlists.get).toHaveBeenCalledWith({filter}); + expect(gmp.portlists.getAll).not.toHaveBeenCalled(); + }); + + test('should not fetch port lists when disabled', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetPortLists({enabled: false}); + return
; + }; + + render(); + + expect(gmp.portlists.get).not.toHaveBeenCalled(); + }); + + test('should not fetch port lists without a session token', () => { + const gmp = createGmp(); + gmp.session.token = undefined; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetPortLists({filter}); + return
; + }; + + render(); + + expect(gmp.portlists.get).not.toHaveBeenCalled(); + }); +}); + +describe('useGetAllPortLists', () => { + test('should fetch all port lists with a filter', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const {data} = useGetAllPortLists({filter}); + return
{data?.entities[0]?.name}
; + }; + + render(); + + await waitFor(() => { + expect(screen.getByTestId('port-list')).toHaveTextContent('Port List 1'); + }); + + expect(gmp.portlists.getAll).toHaveBeenCalledWith({filter}); + expect(gmp.portlists.get).not.toHaveBeenCalled(); + }); + + test('should not fetch port lists when disabled', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetAllPortLists({enabled: false}); + return
; + }; + + render(); + + expect(gmp.portlists.getAll).not.toHaveBeenCalled(); + }); + + test('should not fetch port lists without a session token', () => { + const gmp = createGmp(); + gmp.session.token = undefined; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetAllPortLists({filter}); + return
; + }; + + render(); + + expect(gmp.portlists.getAll).not.toHaveBeenCalled(); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/Roles.test.tsx b/src/web/hooks/use-query/__tests__/Roles.test.tsx index bcad03a074..c40814fb2b 100644 --- a/src/web/hooks/use-query/__tests__/Roles.test.tsx +++ b/src/web/hooks/use-query/__tests__/Roles.test.tsx @@ -7,11 +7,12 @@ import {describe, expect, test, testing} from '@gsa/testing'; import {rendererWith, screen, waitFor} from 'web/testing'; import CollectionCounts from 'gmp/collection/collection-counts'; import QueryFilter from 'gmp/models/filter/query-filter'; +import Role from 'gmp/models/role'; import {createSession} from 'gmp/testing'; import {useGetAllRoles, useGetRoles} from 'web/hooks/use-query/roles'; const filter = QueryFilter.fromString('name~role'); -const roles = [{id: 'role-1', name: 'Role 1'}]; +const roles = [new Role({id: 'role-1', name: 'Role 1'})]; const createGmp = () => { const response = { @@ -32,7 +33,7 @@ const createGmp = () => { }; }; -describe('role query hooks', () => { +describe('useGetRoles', () => { test('should fetch roles with a filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); @@ -52,6 +53,37 @@ describe('role query hooks', () => { expect(gmp.roles.getAll).not.toHaveBeenCalled(); }); + test('should not fetch roles when disabled', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetRoles({enabled: false}); + return
; + }; + + render(); + + expect(gmp.roles.get).not.toHaveBeenCalled(); + }); + + test('should not fetch roles without a session token', () => { + const gmp = createGmp(); + gmp.session.token = undefined; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetRoles({filter}); + return
; + }; + + render(); + + expect(gmp.roles.get).not.toHaveBeenCalled(); + }); +}); + +describe('useGetAllRoles', () => { test('should fetch all roles with a filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); @@ -76,14 +108,12 @@ describe('role query hooks', () => { const {render} = rendererWith({gmp, router: true}); const TestComponent = () => { - useGetRoles({enabled: false}); useGetAllRoles({enabled: false}); return
; }; render(); - expect(gmp.roles.get).not.toHaveBeenCalled(); expect(gmp.roles.getAll).not.toHaveBeenCalled(); }); @@ -93,14 +123,12 @@ describe('role query hooks', () => { const {render} = rendererWith({gmp, router: true}); const TestComponent = () => { - useGetRoles({filter}); useGetAllRoles({filter}); return
; }; render(); - expect(gmp.roles.get).not.toHaveBeenCalled(); expect(gmp.roles.getAll).not.toHaveBeenCalled(); }); }); diff --git a/src/web/hooks/use-query/__tests__/Tag.test.tsx b/src/web/hooks/use-query/__tests__/Tag.test.tsx new file mode 100644 index 0000000000..3e5805fe60 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/Tag.test.tsx @@ -0,0 +1,179 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import Tag from 'gmp/models/tag'; +import {createSession} from 'gmp/testing'; +import { + useCloneTag, + useCreateTag, + useDeleteTag, + useDisableTag, + useEnableTag, + useGetTag, + useSaveTag, +} from 'web/hooks/use-query/tag'; + +const tag = new Tag({id: 'tag-1', name: 'Tag 1', value: 'value'}); + +const createGmp = () => ({ + session: createSession({token: 'test-token'}), + settings: {}, + tag: { + get: testing.fn().mockResolvedValue({data: tag}), + delete: testing.fn().mockResolvedValue(undefined), + enable: testing.fn().mockResolvedValue({data: {}}), + disable: testing.fn().mockResolvedValue({data: {}}), + create: testing.fn().mockResolvedValue({data: {id: 'tag-2'}}), + save: testing.fn().mockResolvedValue({data: {id: 'tag-1'}}), + clone: testing.fn().mockResolvedValue({data: {id: 'tag-2'}}), + }, +}); + +describe('useGetTag', () => { + test('should fetch a tag by ID', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const {data} = useGetTag({id: 'tag-1'}); + return
{data?.name}
; + }; + + render(); + await waitFor(() => { + expect(screen.getByTestId('tag')).toHaveTextContent('Tag 1'); + }); + expect(gmp.tag.get).toHaveBeenCalledWith({id: 'tag-1'}); + }); +}); + +describe('useDeleteTag', () => { + test('should delete a tag', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useDeleteTag({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + await waitFor(() => { + expect(gmp.tag.delete).toHaveBeenCalledWith({id: 'tag-1'}); + }); + }); +}); + +describe('useEnableTag', () => { + test('should enable a tag', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useEnableTag({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + await waitFor(() => { + expect(gmp.tag.enable).toHaveBeenCalledWith({id: 'tag-1'}); + }); + }); +}); + +describe('useDisableTag', () => { + test('should disable a tag', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useDisableTag({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + await waitFor(() => { + expect(gmp.tag.disable).toHaveBeenCalledWith({id: 'tag-1'}); + }); + }); +}); + +describe('useCreateTag', () => { + test('should create a tag', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = { + active: true, + name: 'New Tag', + resourceType: 'tag' as const, + value: 'value', + }; + const TestComponent = () => { + const mutation = useCreateTag({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + await waitFor(() => { + expect(gmp.tag.create).toHaveBeenCalledWith(input); + }); + }); +}); + +describe('useSaveTag', () => { + test('should save a tag', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = { + active: true, + id: 'tag-1', + name: 'Saved Tag', + resourceType: 'tag' as const, + }; + const TestComponent = () => { + const mutation = useSaveTag({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + await waitFor(() => { + expect(gmp.tag.save).toHaveBeenCalledWith(input); + }); + }); +}); + +describe('useCloneTag', () => { + test('should clone a tag by ID', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useCloneTag({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Run'})); + await waitFor(() => { + expect(gmp.tag.clone).toHaveBeenCalledWith({id: 'tag-1'}); + }); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/Tags.test.tsx b/src/web/hooks/use-query/__tests__/Tags.test.tsx index 62210e9faf..48e7fb76eb 100644 --- a/src/web/hooks/use-query/__tests__/Tags.test.tsx +++ b/src/web/hooks/use-query/__tests__/Tags.test.tsx @@ -12,14 +12,7 @@ import {createSession} from 'gmp/testing'; import { useBulkDeleteTags, useBulkExportTags, - useCloneTag, - useCreateTag, - useDeleteTag, - useDisableTag, - useEnableTag, - useGetTag, useGetTags, - useSaveTag, } from 'web/hooks/use-query/tags'; const filter = QueryFilter.fromString('name~tag'); @@ -41,98 +34,67 @@ const createGmp = () => ({ export: testing.fn().mockResolvedValue({data: 'tags'}), exportByFilter: testing.fn().mockResolvedValue({data: 'tags'}), }, - tag: { - get: testing.fn().mockResolvedValue({data: tag}), - delete: testing.fn().mockResolvedValue(undefined), - enable: testing.fn().mockResolvedValue({data: {}}), - disable: testing.fn().mockResolvedValue({data: {}}), - create: testing.fn().mockResolvedValue({data: {id: 'tag-2'}}), - save: testing.fn().mockResolvedValue({data: {id: 'tag-1'}}), - clone: testing.fn().mockResolvedValue({data: {id: 'tag-2'}}), - }, }); -describe('tag query and mutation hooks', () => { - test('should fetch tags and a tag by ID', async () => { +describe('useGetTags', () => { + test('should fetch tags with a filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - const TestComponent = () => { - const tags = useGetTags({filter}); - const singleTag = useGetTag({id: 'tag-1'}); - return ( -
- {tags.data?.entities.length}:{singleTag.data?.name} -
- ); + const {data} = useGetTags({filter}); + return
{data?.entities.length}
; }; render(); - await waitFor(() => { - expect(screen.getByTestId('tags')).toHaveTextContent('1:Tag 1'); + expect(screen.getByTestId('tags')).toHaveTextContent('1'); }); - expect(gmp.tags.get).toHaveBeenCalledWith({filter}); - expect(gmp.tag.get).toHaveBeenCalledWith({id: 'tag-1'}); }); +}); - test.each([ - ['delete', useDeleteTag, 'delete', {id: 'tag-1'}], - ['enable', useEnableTag, 'enable', {id: 'tag-1'}], - ['disable', useDisableTag, 'disable', {id: 'tag-1'}], - ['create', useCreateTag, 'create', {name: 'New Tag', value: 'value'}], - ['save', useSaveTag, 'save', {id: 'tag-1', name: 'Saved Tag'}], - ['clone', useCloneTag, 'clone', {id: 'tag-1'}], - ])('should %s a tag', async (_name, useMutation, method, input) => { +describe('useBulkDeleteTags', () => { + test('should bulk delete tags by list and filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - const TestComponent = () => { - const mutation = useMutation({}); + const mutation = useBulkDeleteTags({}); return ( - + <> + + + ); }; render(); - fireEvent.click(screen.getByRole('button', {name: 'Run'})); - + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + fireEvent.click(screen.getByRole('button', {name: 'Delete filter'})); await waitFor(() => { - expect(gmp.tag[method]).toHaveBeenCalledWith(input); + expect(gmp.tags.delete).toHaveBeenCalledWith([tag]); + expect(gmp.tags.deleteByFilter).toHaveBeenCalledWith(filter); }); }); +}); - test('should bulk delete and export tags by list and filter', async () => { +describe('useBulkExportTags', () => { + test('should bulk export tags by list and filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - const TestComponent = () => { - const deleteMutation = useBulkDeleteTags({}); - const exportMutation = useBulkExportTags({}); + const mutation = useBulkExportTags({}); return ( <> - - - - + + ); }; render(); - fireEvent.click(screen.getByRole('button', {name: 'Delete'})); - fireEvent.click(screen.getByRole('button', {name: 'Delete filter'})); fireEvent.click(screen.getByRole('button', {name: 'Export'})); fireEvent.click(screen.getByRole('button', {name: 'Export filter'})); - await waitFor(() => { - expect(gmp.tags.delete).toHaveBeenCalledWith([tag]); - expect(gmp.tags.deleteByFilter).toHaveBeenCalledWith(filter); expect(gmp.tags.export).toHaveBeenCalledWith([tag]); expect(gmp.tags.exportByFilter).toHaveBeenCalledWith(filter); }); diff --git a/src/web/hooks/use-query/__tests__/Target.test.tsx b/src/web/hooks/use-query/__tests__/Target.test.tsx new file mode 100644 index 0000000000..7a9162d845 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/Target.test.tsx @@ -0,0 +1,211 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import Target from 'gmp/models/target'; +import {createSession} from 'gmp/testing'; +import { + useCloneTarget, + useDeleteTarget, + useGetTarget, +} from 'web/hooks/use-query/target'; + +const target = new Target({id: 'target-1', name: 'Target 1'}); + +const createGmp = ({token}: {token?: string} = {token: 'test-token'}) => ({ + session: createSession({token}), + settings: {}, + target: { + get: testing.fn().mockResolvedValue({data: target}), + clone: testing.fn().mockResolvedValue({data: {id: 'target-2'}}), + delete: testing.fn().mockResolvedValue(undefined), + }, +}); + +describe('useGetTarget', () => { + test('should fetch a target by ID', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const {data} = useGetTarget({id: target.id}); + return
{data?.name}
; + }; + + render(); + + await waitFor(() => { + expect(screen.getByTestId('target')).toHaveTextContent('Target 1'); + }); + + expect(gmp.target.get).toHaveBeenCalledWith({id: target.id}); + }); + + test('should not fetch when the ID is empty', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetTarget({id: ''}); + return
; + }; + + render(); + + expect(gmp.target.get).not.toHaveBeenCalled(); + }); + + test('should not fetch without a session token', () => { + const gmp = createGmp({token: undefined}); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetTarget({id: target.id}); + return
; + }; + + render(); + + expect(gmp.target.get).not.toHaveBeenCalled(); + }); + + test('should show an error when fetching a target fails', async () => { + const gmp = createGmp(); + gmp.target.get.mockRejectedValue(new Error('Request failed')); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const {isError} = useGetTarget({id: target.id}); + return
{isError ? 'Error' : ''}
; + }; + + render(); + + await waitFor(() => { + expect(screen.getByTestId('error')).toHaveTextContent('Error'); + }); + }); +}); + +describe('useCloneTarget', () => { + test('should clone a target with its name and call onSuccess', async () => { + const gmp = createGmp(); + const onSuccess = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCloneTarget({onSuccess}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Clone'})); + + await waitFor(() => { + expect(gmp.target.clone).toHaveBeenCalledWith({ + id: 'target-1', + }); + expect(onSuccess).toHaveBeenCalledWith({id: 'target-2'}); + expect( + screen.getByText('Target Cloned Target cloned successfully'), + ).toBeInTheDocument(); + }); + }); + + test('should call onError when cloning a target fails', async () => { + const error = new Error('Clone failed'); + const gmp = createGmp(); + gmp.target.clone.mockRejectedValue(error); + const onError = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCloneTarget({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Clone'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); + +describe('useDeleteTarget', () => { + test('should delete a target with its name and call onSuccess', async () => { + const gmp = createGmp(); + const onSuccess = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useDeleteTarget({onSuccess}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => { + expect(gmp.target.delete).toHaveBeenCalledWith({ + id: 'target-1', + }); + expect(onSuccess).toHaveBeenCalledWith(undefined); + expect( + screen.getByText('Target Deleted Target successfully deleted'), + ).toBeInTheDocument(); + }); + }); + + test('should call onError when deleting a target fails', async () => { + const error = new Error('Delete failed'); + const gmp = createGmp(); + gmp.target.delete.mockRejectedValue(error); + const onError = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useDeleteTarget({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/Targets.test.tsx b/src/web/hooks/use-query/__tests__/Targets.test.tsx new file mode 100644 index 0000000000..dbd88f5482 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/Targets.test.tsx @@ -0,0 +1,134 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {rendererWith, screen, waitFor} from 'web/testing'; +import CollectionCounts from 'gmp/collection/collection-counts'; +import QueryFilter from 'gmp/models/filter/query-filter'; +import Target from 'gmp/models/target'; +import {createSession} from 'gmp/testing'; +import {useGetAllTargets, useGetTargets} from 'web/hooks/use-query/targets'; + +const filter = QueryFilter.fromString('name~target'); +const targets = [new Target({id: 'target-1', name: 'Target 1'})]; + +const createGmp = () => { + const response = { + data: targets, + meta: { + filter, + counts: new CollectionCounts({all: 1, filtered: 1, length: 1}), + }, + }; + + return { + session: createSession({token: 'test-token'}), + settings: {}, + targets: { + get: testing.fn().mockResolvedValue(response), + getAll: testing.fn().mockResolvedValue(response), + }, + }; +}; + +describe('useGetTargets', () => { + test('should fetch targets with a filter', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const {data} = useGetTargets({filter}); + return
{data?.entities[0]?.name}
; + }; + + render(); + + await waitFor(() => { + expect(screen.getByTestId('target')).toHaveTextContent('Target 1'); + }); + + expect(gmp.targets.get).toHaveBeenCalledWith({filter}); + expect(gmp.targets.getAll).not.toHaveBeenCalled(); + }); + + test('should not fetch targets when disabled', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetTargets({enabled: false}); + return
; + }; + + render(); + + expect(gmp.targets.get).not.toHaveBeenCalled(); + }); + + test('should not fetch targets without a session token', () => { + const gmp = createGmp(); + gmp.session.token = undefined; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetTargets({filter}); + return
; + }; + + render(); + + expect(gmp.targets.get).not.toHaveBeenCalled(); + }); +}); + +describe('useGetAllTargets', () => { + test('should fetch all targets with a filter', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const {data} = useGetAllTargets({filter}); + return
{data?.entities[0]?.name}
; + }; + + render(); + + await waitFor(() => { + expect(screen.getByTestId('target')).toHaveTextContent('Target 1'); + }); + + expect(gmp.targets.getAll).toHaveBeenCalledWith({filter}); + expect(gmp.targets.get).not.toHaveBeenCalled(); + }); + + test('should not fetch targets when disabled', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetAllTargets({enabled: false}); + return
; + }; + + render(); + + expect(gmp.targets.getAll).not.toHaveBeenCalled(); + }); + + test('should not fetch targets without a session token', () => { + const gmp = createGmp(); + gmp.session.token = undefined; + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetAllTargets({filter}); + return
; + }; + + render(); + + expect(gmp.targets.getAll).not.toHaveBeenCalled(); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/Ticket.test.tsx b/src/web/hooks/use-query/__tests__/Ticket.test.tsx new file mode 100644 index 0000000000..d26f546af4 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/Ticket.test.tsx @@ -0,0 +1,298 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, test, expect, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import Ticket, {TICKET_STATUS} from 'gmp/models/ticket'; +import {createSession} from 'gmp/testing'; +import { + useCloneTicket, + useCreateTicket, + useDeleteTicket, + useDownloadTicket, + useGetTicket, + useSaveTicket, +} from 'web/hooks/use-query/ticket'; + +const ticket = Ticket.fromElement({ + _id: 'ticket-1', + name: 'Test Ticket', + status: TICKET_STATUS.open, + assigned_to: {user: {_id: 'u1', name: 'admin'}}, + open_time: '2024-01-10T08:00:00Z', + open_note: 'Ticket opened', +}); + +const SingleTicketComponent = ({id}: {id: string}) => { + const {data, isLoading, isError} = useGetTicket({id}); + + if (isLoading) return
Loading...
; + if (isError) return
Error
; + if (!data) return
No data
; + + return ( +
+ {data.name} + {data.id} +
+ ); +}; + +const createGmp = ({token}: {token?: string} = {token: 'test-token'}) => ({ + session: createSession({token}), + settings: {}, + ticket: { + get: testing.fn().mockResolvedValue({data: ticket}), + create: testing.fn().mockResolvedValue({data: {id: 'ticket-3'}}), + save: testing.fn().mockResolvedValue({data: {id: 'ticket-1'}}), + clone: testing.fn().mockResolvedValue({data: {id: 'ticket-3'}}), + delete: testing.fn().mockResolvedValue(undefined), + export: testing.fn().mockResolvedValue({data: 'ticket-content'}), + }, +}); + +describe('useGetTicket', () => { + test('should fetch a single ticket', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + render(); + + await waitFor(() => { + expect(screen.getByTestId('ticket-name')).toHaveTextContent( + 'Test Ticket', + ); + }); + + expect(gmp.ticket.get).toHaveBeenCalledWith({id: 'ticket-1'}); + expect(screen.getByTestId('ticket-id')).toHaveTextContent('ticket-1'); + }); + + test('should show loading state initially', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + render(); + + expect(screen.getByTestId('loading')).toBeInTheDocument(); + }); + + test('should not fetch a ticket when the ID is empty', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + render(); + + expect(screen.getByTestId('no-data')).toBeInTheDocument(); + expect(gmp.ticket.get).not.toHaveBeenCalled(); + }); + + test('should not fetch a ticket without a session token', () => { + const gmp = createGmp({token: undefined}); + const {render} = rendererWith({gmp, router: true}); + + render(); + + expect(gmp.ticket.get).not.toHaveBeenCalled(); + }); + + test('should show an error when fetching a ticket fails', async () => { + const gmp = createGmp(); + gmp.ticket.get.mockRejectedValue(new Error('Request failed')); + const {render} = rendererWith({gmp, router: true}); + + render(); + + await waitFor(() => { + expect(screen.getByTestId('error')).toBeInTheDocument(); + }); + }); +}); + +describe('useCreateTicket', () => { + test('should create a ticket', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {resultId: 'result-1', userId: 'user-1', note: 'Note'}; + + const TestComponent = () => { + const mutation = useCreateTicket(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => { + expect(gmp.ticket.create).toHaveBeenCalledWith(input); + }); + }); +}); + +describe('useSaveTicket', () => { + test('should save a ticket', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = { + id: 'ticket-1', + status: TICKET_STATUS.fixed, + userId: 'user-1', + fixedNote: 'Fixed', + }; + + const TestComponent = () => { + const mutation = useSaveTicket(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Save'})); + + await waitFor(() => { + expect(gmp.ticket.save).toHaveBeenCalledWith(input); + }); + }); +}); + +describe('useCloneTicket', () => { + test('should clone a ticket with its name and call onSuccess', async () => { + const gmp = createGmp(); + const onSuccess = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCloneTicket({onSuccess}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Clone'})); + + await waitFor(() => { + expect(gmp.ticket.clone).toHaveBeenCalledWith({ + id: 'ticket-1', + }); + expect(onSuccess).toHaveBeenCalledWith({id: 'ticket-3'}); + expect( + screen.getByText('Ticket Cloned Ticket cloned successfully'), + ).toBeInTheDocument(); + }); + }); + + test('should call onError when cloning a ticket fails', async () => { + const error = new Error('Clone failed'); + const gmp = createGmp(); + gmp.ticket.clone.mockRejectedValue(error); + const onError = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCloneTicket({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Clone'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); + +describe('useDeleteTicket', () => { + test('should delete a ticket with its name and call onSuccess', async () => { + const gmp = createGmp(); + const onSuccess = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useDeleteTicket({onSuccess}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => { + expect(gmp.ticket.delete).toHaveBeenCalledWith({ + id: 'ticket-1', + }); + expect(onSuccess).toHaveBeenCalledWith(undefined); + expect( + screen.getByText('Ticket Deleted Ticket successfully deleted'), + ).toBeInTheDocument(); + }); + }); + + test('should call onError when deleting a ticket fails', async () => { + const error = new Error('Delete failed'); + const gmp = createGmp(); + gmp.ticket.delete.mockRejectedValue(error); + const onError = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useDeleteTicket({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); + +describe('useDownloadTicket', () => { + test('should download a ticket', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useDownloadTicket(); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Download'})); + + await waitFor(() => { + expect(gmp.ticket.export).toHaveBeenCalledWith({id: 'ticket-1'}); + }); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/Tickets.test.tsx b/src/web/hooks/use-query/__tests__/Tickets.test.tsx index 7f15e3e4e1..07349a97a7 100644 --- a/src/web/hooks/use-query/__tests__/Tickets.test.tsx +++ b/src/web/hooks/use-query/__tests__/Tickets.test.tsx @@ -4,21 +4,13 @@ */ import {describe, test, expect, testing} from '@gsa/testing'; -import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import {rendererWith, screen, waitFor} from 'web/testing'; import CollectionCounts from 'gmp/collection/collection-counts'; import type FilterType from 'gmp/models/filter/filter-type'; import QueryFilter from 'gmp/models/filter/query-filter'; import Ticket, {TICKET_STATUS} from 'gmp/models/ticket'; import {createSession} from 'gmp/testing'; -import { - useCloneTicket, - useCreateTicket, - useDeleteTicket, - useDownloadTicket, - useGetTicket, - useGetTickets, - useSaveTicket, -} from 'web/hooks/use-query/tickets'; +import {useGetTickets} from 'web/hooks/use-query/tickets'; const ticket = Ticket.fromElement({ _id: 'ticket-1', @@ -40,27 +32,6 @@ const ticket2 = Ticket.fromElement({ const filter = QueryFilter.fromString('name~test'); -const SingleTicketComponent = ({id}: {id: string}) => { - const {data, isLoading, isError} = useGetTicket({id}); - - if (isLoading) { - return
Loading...
; - } - if (isError) { - return
Error
; - } - if (!data) { - return
No data
; - } - - return ( -
- {data.name} - {data.id} -
- ); -}; - const TicketListComponent = ({filter}: {filter?: FilterType}) => { const {data, isLoading, isError} = useGetTickets({filter}); @@ -88,14 +59,6 @@ const TicketListComponent = ({filter}: {filter?: FilterType}) => { const createGmp = ({token}: {token?: string} = {token: 'test-token'}) => ({ session: createSession({token}), settings: {}, - ticket: { - get: testing.fn().mockResolvedValue({data: ticket}), - create: testing.fn().mockResolvedValue({data: {id: 'ticket-3'}}), - save: testing.fn().mockResolvedValue({data: {id: 'ticket-1'}}), - clone: testing.fn().mockResolvedValue({data: {id: 'ticket-3'}}), - delete: testing.fn().mockResolvedValue(undefined), - export: testing.fn().mockResolvedValue({data: 'ticket-content'}), - }, tickets: { get: testing.fn().mockResolvedValue({ data: [ticket, ticket2], @@ -107,62 +70,6 @@ const createGmp = ({token}: {token?: string} = {token: 'test-token'}) => ({ }, }); -describe('useGetTicket', () => { - test('should fetch a single ticket', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - render(); - - await waitFor(() => { - expect(screen.getByTestId('ticket-name')).toHaveTextContent( - 'Test Ticket', - ); - }); - - expect(gmp.ticket.get).toHaveBeenCalledWith({id: 'ticket-1'}); - expect(screen.getByTestId('ticket-id')).toHaveTextContent('ticket-1'); - }); - - test('should show loading state initially', () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - render(); - - expect(screen.getByTestId('loading')).toBeInTheDocument(); - }); - - test('should not fetch a ticket when the ID is empty', () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - render(); - - expect(screen.getByTestId('no-data')).toBeInTheDocument(); - expect(gmp.ticket.get).not.toHaveBeenCalled(); - }); - - test('should not fetch a ticket without a session token', () => { - const gmp = createGmp({token: undefined}); - const {render} = rendererWith({gmp, router: true}); - - render(); - - expect(gmp.ticket.get).not.toHaveBeenCalled(); - }); - - test('should show an error when fetching a ticket fails', async () => { - const gmp = createGmp(); - gmp.ticket.get.mockRejectedValue(new Error('Request failed')); - const {render} = rendererWith({gmp, router: true}); - - render(); - - await waitFor(() => { - expect(screen.getByTestId('error')).toBeInTheDocument(); - }); - }); -}); - describe('useGetTickets', () => { test('should fetch a list of tickets', async () => { const gmp = createGmp(); @@ -199,107 +106,3 @@ describe('useGetTickets', () => { }); }); }); - -describe('ticket mutation hooks', () => { - test('should create a ticket', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - const input = {resultId: 'result-1', userId: 'user-1', note: 'Note'}; - - const TestComponent = () => { - const mutation = useCreateTicket(); - return ; - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Create'})); - - await waitFor(() => { - expect(gmp.ticket.create).toHaveBeenCalledWith(input); - }); - }); - - test('should save a ticket', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - const input = { - id: 'ticket-1', - status: TICKET_STATUS.fixed, - userId: 'user-1', - fixedNote: 'Fixed', - }; - - const TestComponent = () => { - const mutation = useSaveTicket(); - return ; - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Save'})); - - await waitFor(() => { - expect(gmp.ticket.save).toHaveBeenCalledWith(input); - }); - }); - - test('should clone a ticket', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useCloneTicket(); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Clone'})); - - await waitFor(() => { - expect(gmp.ticket.clone).toHaveBeenCalledWith({id: 'ticket-1'}); - }); - }); - - test('should delete a ticket', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useDeleteTicket(); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Delete'})); - - await waitFor(() => { - expect(gmp.ticket.delete).toHaveBeenCalledWith({id: 'ticket-1'}); - }); - }); - - test('should download a ticket', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useDownloadTicket(); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Download'})); - - await waitFor(() => { - expect(gmp.ticket.export).toHaveBeenCalledWith({id: 'ticket-1'}); - }); - }); -}); diff --git a/src/web/hooks/use-query/__tests__/User.test.tsx b/src/web/hooks/use-query/__tests__/User.test.tsx new file mode 100644 index 0000000000..f7202c9951 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/User.test.tsx @@ -0,0 +1,340 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import User from 'gmp/models/user'; +import {createSession} from 'gmp/testing'; +import { + useCloneUser, + useCreateUser, + useDeleteUser, + useDownloadUser, + useGetUser, + useSaveUser, +} from 'web/hooks/use-query/user'; + +const userData = { + accessHosts: ['localhost', '127.0.0.1'], + authMethod: 'password', + comment: 'comment', + groupIds: ['group-id'], + hostsAllow: '0', + name: 'user', + password: 'password', + roleIds: ['role-id'], +}; + +const user = new User({id: 'user-id', name: 'user'}); + +const createGmp = ( + userMethods: Record = {}, + token = 'test-token', +) => ({ + session: createSession({token}), + settings: {}, + user: { + get: testing.fn().mockResolvedValue({data: user}), + create: testing.fn().mockResolvedValue({data: {id: 'created'}}), + save: testing.fn().mockResolvedValue({data: {id: 'saved'}}), + clone: testing.fn().mockResolvedValue({data: {id: 'cloned'}}), + delete: testing.fn().mockResolvedValue(undefined), + export: testing.fn().mockResolvedValue({data: 'user-content'}), + ...userMethods, + }, +}); + +describe('useGetUser', () => { + test('should fetch a user by ID', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const {data} = useGetUser({id: 'user-id'}); + return
{data?.name}
; + }; + + render(); + + await waitFor(() => { + expect(screen.getByTestId('user')).toHaveTextContent('user'); + }); + + expect(gmp.user.get).toHaveBeenCalledWith({id: 'user-id'}); + }); + + test('should not fetch a user without a session token', () => { + const gmp = createGmp({}, ''); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetUser({id: 'user-id'}); + return
; + }; + + render(); + + expect(gmp.user.get).not.toHaveBeenCalled(); + }); + + test('should not fetch a user when the ID is empty', () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + useGetUser({id: ''}); + return
; + }; + + render(); + + expect(gmp.user.get).not.toHaveBeenCalled(); + }); +}); + +describe('useCreateUser', () => { + test('forwards access_hosts arrays when creating a user', async () => { + const create = testing.fn().mockResolvedValue({data: {id: 'created'}}); + const gmp = createGmp({create}); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreateUser(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => expect(create).toHaveBeenCalledWith(userData)); + }); + + test('calls onSuccess with created user data', async () => { + const onSuccess = testing.fn(); + const gmp = createGmp({onSuccess: undefined}); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreateUser({onSuccess}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => + expect(onSuccess).toHaveBeenCalledWith({id: 'created'}), + ); + }); + + test('calls onError when creating a user fails', async () => { + const error = new Error('Create failed'); + const onError = testing.fn(); + const gmp = createGmp({create: testing.fn().mockRejectedValue(error)}); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCreateUser({onError}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); + +describe('useSaveUser', () => { + test('derives oldName when saving', async () => { + const save = testing.fn().mockResolvedValue({data: {id: 'saved'}}); + const gmp = createGmp({save}); + const {render} = rendererWith({gmp, router: true}); + const input = {...userData, id: 'user-id'}; + + const TestComponent = () => { + const mutation = useSaveUser(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Save'})); + + await waitFor(() => { + expect(save).toHaveBeenCalledWith({...input, oldName: userData.name}); + }); + }); + + test('preserves an explicit oldName when saving', async () => { + const save = testing.fn().mockResolvedValue({data: {id: 'saved'}}); + const gmp = createGmp({save}); + const {render} = rendererWith({gmp, router: true}); + const input = {...userData, id: 'user-id', oldName: 'previous-user'}; + + const TestComponent = () => { + const mutation = useSaveUser(); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Save'})); + + await waitFor(() => expect(save).toHaveBeenCalledWith(input)); + }); + + test('calls callbacks when saving fails', async () => { + const error = new Error('Save failed'); + const onError = testing.fn(); + const save = testing.fn().mockRejectedValue(error); + const gmp = createGmp({save}); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useSaveUser({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Save'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); + +describe('useCloneUser', () => { + test('should clone a user with its name and call onSuccess', async () => { + const gmp = createGmp(); + const onSuccess = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useCloneUser({onSuccess}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Clone'})); + + await waitFor(() => { + expect(gmp.user.clone).toHaveBeenCalledWith({id: 'user-id'}); + expect(onSuccess).toHaveBeenCalledWith({id: 'cloned'}); + expect( + screen.getByText('User Cloned User cloned successfully'), + ).toBeInTheDocument(); + }); + }); + + test('should call onError when cloning a user fails', async () => { + const error = new Error('Clone failed'); + const gmp = createGmp({clone: testing.fn().mockRejectedValue(error)}); + const onError = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useCloneUser({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Clone'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); + +describe('useDeleteUser', () => { + test('should delete a user with its name and forward the inheritor ID', async () => { + const gmp = createGmp(); + const onSuccess = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + const input = { + id: 'user-id', + name: 'Deleted User', + inheritorId: 'inheritor-id', + }; + const TestComponent = () => { + const mutation = useDeleteUser({onSuccess}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => { + expect(gmp.user.delete).toHaveBeenCalledWith({ + id: 'user-id', + inheritorId: 'inheritor-id', + }); + expect(onSuccess).toHaveBeenCalledWith(undefined); + expect( + screen.getByText('User Deleted User successfully deleted'), + ).toBeInTheDocument(); + }); + }); + + test('should call onError when deleting a user fails', async () => { + const error = new Error('Delete failed'); + const gmp = createGmp({ + delete: testing.fn().mockRejectedValue(error), + }); + const onError = testing.fn(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useDeleteUser({onError}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => expect(onError.mock.calls[0][0]).toBe(error)); + }); +}); + +describe('useDownloadUser', () => { + test('should download a user', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const TestComponent = () => { + const mutation = useDownloadUser(); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Download'})); + await waitFor(() => + expect(gmp.user.export).toHaveBeenCalledWith({id: 'user-id'}), + ); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/Users.test.tsx b/src/web/hooks/use-query/__tests__/Users.test.tsx index bd9bb7a08e..fcf9e31396 100644 --- a/src/web/hooks/use-query/__tests__/Users.test.tsx +++ b/src/web/hooks/use-query/__tests__/Users.test.tsx @@ -12,27 +12,10 @@ import {createSession} from 'gmp/testing'; import { useBulkDeleteUsers, useBulkExportUsers, - useCloneUser, - useCreateUser, - useDeleteUser, - useDownloadUser, - useGetUser, useGetUsers, - useSaveUser, } from 'web/hooks/use-query/users'; -const userData = { - accessHosts: ['localhost', '127.0.0.1'], - authMethod: 'password', - comment: 'comment', - groupIds: ['group-id'], - hostsAllow: '0', - name: 'user', - password: 'password', - roleIds: ['role-id'], -}; - -const user = User.fromElement({_id: 'user-id', name: 'user'}); +const user = new User({id: 'user-id', name: 'user'}); const userFilter = QueryFilter.fromString('name~user'); const createGmp = ( @@ -41,15 +24,7 @@ const createGmp = ( ) => ({ session: createSession({token}), settings: {}, - user: { - get: testing.fn().mockResolvedValue({data: user}), - create: testing.fn().mockResolvedValue({data: {id: 'created'}}), - save: testing.fn().mockResolvedValue({data: {id: 'saved'}}), - clone: testing.fn().mockResolvedValue({data: {id: 'cloned'}}), - delete: testing.fn().mockResolvedValue(undefined), - export: testing.fn().mockResolvedValue({data: 'user-content'}), - ...userMethods, - }, + user: userMethods, users: { get: testing.fn().mockResolvedValue({ data: [user], @@ -64,7 +39,7 @@ const createGmp = ( }, }); -describe('user query hooks', () => { +describe('useGetUsers', () => { test('should fetch users with a filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); @@ -83,24 +58,6 @@ describe('user query hooks', () => { expect(gmp.users.get).toHaveBeenCalledWith({filter: userFilter}); }); - test('should fetch a user by ID', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const {data} = useGetUser({id: 'user-id'}); - return
{data?.name}
; - }; - - render(); - - await waitFor(() => { - expect(screen.getByTestId('user')).toHaveTextContent('user'); - }); - - expect(gmp.user.get).toHaveBeenCalledWith({id: 'user-id'}); - }); - test('should not fetch users without a session token', () => { const gmp = createGmp({}, ''); const {render} = rendererWith({gmp, router: true}); @@ -114,239 +71,9 @@ describe('user query hooks', () => { expect(gmp.users.get).not.toHaveBeenCalled(); }); - - test('should not fetch a user when the ID is empty', () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - useGetUser({id: ''}); - return
; - }; - - render(); - - expect(gmp.user.get).not.toHaveBeenCalled(); - }); }); -describe('useCreateUser', () => { - test('forwards access_hosts arrays when creating a user', async () => { - const create = testing.fn().mockResolvedValue({data: {id: 'created'}}); - const gmp = createGmp({create}); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useCreateUser(); - return ; - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Create'})); - - await waitFor(() => { - expect(create).toHaveBeenCalledWith(userData); - }); - }); - test('calls onSuccess with created user data', async () => { - const create = testing.fn().mockResolvedValue({data: {id: 'created'}}); - const onSuccess = testing.fn(); - const gmp = createGmp({create}); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useCreateUser({onSuccess}); - return ; - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Create'})); - - await waitFor(() => { - expect(onSuccess).toHaveBeenCalledWith({id: 'created'}); - }); - }); - - test('calls onError when creating a user fails', async () => { - const error = new Error('Create failed'); - const create = testing.fn().mockRejectedValue(error); - const onError = testing.fn(); - const gmp = createGmp({create}); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useCreateUser({onError}); - return ; - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Create'})); - - await waitFor(() => { - expect(onError).toHaveBeenCalled(); - expect(onError.mock.calls[0][0]).toBe(error); - }); - }); -}); - -describe('useSaveUser', () => { - test('forwards accessHosts arrays and derives oldName when saving', async () => { - const save = testing.fn().mockResolvedValue({data: {id: 'saved'}}); - const gmp = createGmp({save}); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useSaveUser(); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Save'})); - - await waitFor(() => { - expect(save).toHaveBeenCalledWith({ - ...userData, - id: 'user-id', - oldName: userData.name, - }); - }); - }); - - test('preserves an explicit oldName when saving', async () => { - const save = testing.fn().mockResolvedValue({data: {id: 'saved'}}); - const gmp = createGmp({save}); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useSaveUser(); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Save'})); - - await waitFor(() => { - expect(save).toHaveBeenCalledWith({ - ...userData, - id: 'user-id', - oldName: 'previous-user', - }); - }); - }); - - test('calls onSuccess with saved user data', async () => { - const save = testing.fn().mockResolvedValue({data: {id: 'saved'}}); - const onSuccess = testing.fn(); - const gmp = createGmp({save}); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useSaveUser({onSuccess}); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Save'})); - - await waitFor(() => { - expect(onSuccess).toHaveBeenCalledWith({id: 'saved'}); - }); - }); - - test('calls onError when saving a user fails', async () => { - const error = new Error('Save failed'); - const save = testing.fn().mockRejectedValue(error); - const onError = testing.fn(); - const gmp = createGmp({save}); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useSaveUser({onError}); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Save'})); - - await waitFor(() => { - expect(onError).toHaveBeenCalled(); - expect(onError.mock.calls[0][0]).toBe(error); - }); - }); -}); - -describe('remaining user mutation hooks', () => { - test('should clone a user', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useCloneUser(); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Clone'})); - - await waitFor(() => { - expect(gmp.user.clone).toHaveBeenCalledWith({id: 'user-id'}); - }); - }); - - test('should delete a user and forward the inheritor ID', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useDeleteUser(); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Delete'})); - - await waitFor(() => { - expect(gmp.user.delete).toHaveBeenCalledWith({ - id: 'user-id', - inheritorId: 'inheritor-id', - }); - }); - }); - +describe('useBulkDeleteUsers', () => { test('should bulk delete users with options', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); @@ -367,28 +94,9 @@ describe('remaining user mutation hooks', () => { expect(gmp.users.delete).toHaveBeenCalledWith(input.users, input.options); }); }); +}); - test('should download a user', async () => { - const gmp = createGmp(); - const {render} = rendererWith({gmp, router: true}); - - const TestComponent = () => { - const mutation = useDownloadUser(); - return ( - - ); - }; - - render(); - fireEvent.click(screen.getByRole('button', {name: 'Download'})); - - await waitFor(() => { - expect(gmp.user.export).toHaveBeenCalledWith({id: 'user-id'}); - }); - }); - +describe('useBulkExportUsers', () => { test('should bulk export users by entity list and filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); diff --git a/src/web/hooks/use-query/__tests__/WebApplicationTarget.test.tsx b/src/web/hooks/use-query/__tests__/WebApplicationTarget.test.tsx new file mode 100644 index 0000000000..2e7e241ff1 --- /dev/null +++ b/src/web/hooks/use-query/__tests__/WebApplicationTarget.test.tsx @@ -0,0 +1,117 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {describe, expect, test, testing} from '@gsa/testing'; +import {fireEvent, rendererWith, screen, waitFor} from 'web/testing'; +import {createSession} from 'gmp/testing'; +import { + useCloneWebApplicationTarget, + useCreateWebApplicationTarget, + useDeleteWebApplicationTarget, + useSaveWebApplicationTarget, +} from 'web/hooks/use-query/web-application-target'; + +const target = {id: 'target-1', name: 'Target 1'}; + +const createGmp = () => ({ + session: createSession({token: 'test-token'}), + settings: {}, + webapplicationtarget: { + create: testing.fn().mockResolvedValue({data: {id: 'target-2'}}), + save: testing.fn().mockResolvedValue({data: {id: 'target-1'}}), + delete: testing.fn().mockResolvedValue(undefined), + clone: testing.fn().mockResolvedValue({data: {id: 'target-2'}}), + }, +}); + +describe('useCreateWebApplicationTarget', () => { + test('should create a web application target', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {name: 'New Target', urls: 'https://example.com'}; + + const TestComponent = () => { + const mutation = useCreateWebApplicationTarget({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Create'})); + + await waitFor(() => { + expect(gmp.webapplicationtarget.create.mock.calls[0][0]).toEqual(input); + }); + }); +}); + +describe('useSaveWebApplicationTarget', () => { + test('should save a web application target', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + const input = {id: target.id, name: 'Saved'}; + + const TestComponent = () => { + const mutation = useSaveWebApplicationTarget({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Save'})); + + await waitFor(() => { + expect(gmp.webapplicationtarget.save.mock.calls[0][0]).toEqual(input); + }); + }); +}); + +describe('useDeleteWebApplicationTarget', () => { + test('should delete a web application target', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useDeleteWebApplicationTarget({}); + return ; + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Delete'})); + + await waitFor(() => { + expect(gmp.webapplicationtarget.delete).toHaveBeenCalledWith({ + id: target.id, + }); + }); + }); +}); + +describe('useCloneWebApplicationTarget', () => { + test('should clone a web application target', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useCloneWebApplicationTarget({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Clone'})); + + await waitFor(() => { + expect(gmp.webapplicationtarget.clone).toHaveBeenCalledWith({ + id: target.id, + }); + }); + }); +}); diff --git a/src/web/hooks/use-query/__tests__/WebApplicationTargets.test.tsx b/src/web/hooks/use-query/__tests__/WebApplicationTargets.test.tsx index 41cebe61c7..69290f57a8 100644 --- a/src/web/hooks/use-query/__tests__/WebApplicationTargets.test.tsx +++ b/src/web/hooks/use-query/__tests__/WebApplicationTargets.test.tsx @@ -11,11 +11,7 @@ import {createSession} from 'gmp/testing'; import { useBulkDeleteWebApplicationTargets, useBulkExportWebApplicationTargets, - useCloneWebApplicationTarget, - useCreateWebApplicationTarget, - useDeleteWebApplicationTarget, useGetWebApplicationTargets, - useSaveWebApplicationTarget, } from 'web/hooks/use-query/web-application-targets'; const filter = QueryFilter.fromString('name~target'); @@ -37,15 +33,9 @@ const createGmp = () => ({ export: testing.fn().mockResolvedValue({data: 'targets'}), exportByFilter: testing.fn().mockResolvedValue({data: 'targets'}), }, - webapplicationtarget: { - create: testing.fn().mockResolvedValue({data: {id: 'target-2'}}), - save: testing.fn().mockResolvedValue({data: {id: 'target-1'}}), - delete: testing.fn().mockResolvedValue(undefined), - clone: testing.fn().mockResolvedValue({data: {id: 'target-2'}}), - }, }); -describe('web application target query hooks', () => { +describe('useGetWebApplicationTargets', () => { test('should fetch web application targets with a filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); @@ -63,62 +53,49 @@ describe('web application target query hooks', () => { expect(gmp.webapplicationtargets.get).toHaveBeenCalledWith({filter}); }); +}); - test('should forward web application target mutations', async () => { +describe('useBulkDeleteWebApplicationTargets', () => { + test('should delete web application targets by filter', async () => { const gmp = createGmp(); const {render} = rendererWith({gmp, router: true}); - const input = {name: 'New Target', urls: 'https://example.com'}; const TestComponent = () => { - const create = useCreateWebApplicationTarget({}); - const save = useSaveWebApplicationTarget({}); - const remove = useDeleteWebApplicationTarget({}); - const clone = useCloneWebApplicationTarget({}); - const bulkDelete = useBulkDeleteWebApplicationTargets({}); - const bulkExport = useBulkExportWebApplicationTargets({}); + const mutation = useBulkDeleteWebApplicationTargets({}); return ( - <> - - - - - - - + ); }; render(); - fireEvent.click(screen.getByRole('button', {name: 'Create'})); - fireEvent.click(screen.getByRole('button', {name: 'Save'})); fireEvent.click(screen.getByRole('button', {name: 'Delete'})); - fireEvent.click(screen.getByRole('button', {name: 'Clone'})); - fireEvent.click(screen.getByRole('button', {name: 'Bulk delete'})); - fireEvent.click(screen.getByRole('button', {name: 'Bulk export'})); await waitFor(() => { - expect(gmp.webapplicationtarget.create.mock.calls[0][0]).toEqual(input); - expect(gmp.webapplicationtarget.save.mock.calls[0][0]).toEqual({ - id: 'target-1', - name: 'Saved', - }); - expect(gmp.webapplicationtarget.delete).toHaveBeenCalledWith({ - id: 'target-1', - }); - expect(gmp.webapplicationtarget.clone).toHaveBeenCalledWith({ - id: 'target-1', - }); expect(gmp.webapplicationtargets.deleteByFilter).toHaveBeenCalledWith( filter, ); + }); + }); +}); + +describe('useBulkExportWebApplicationTargets', () => { + test('should export web application targets', async () => { + const gmp = createGmp(); + const {render} = rendererWith({gmp, router: true}); + + const TestComponent = () => { + const mutation = useBulkExportWebApplicationTargets({}); + return ( + + ); + }; + + render(); + fireEvent.click(screen.getByRole('button', {name: 'Export'})); + + await waitFor(() => { expect(gmp.webapplicationtargets.export).toHaveBeenCalledWith([target]); }); }); diff --git a/src/web/hooks/use-query/agent-group.ts b/src/web/hooks/use-query/agent-group.ts new file mode 100644 index 0000000000..3cfdcfb298 --- /dev/null +++ b/src/web/hooks/use-query/agent-group.ts @@ -0,0 +1,90 @@ +/* SPDX-FileCopyrightText: 2025 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + type AgentGroupCreateParams, + type AgentGroupSaveParams, +} from 'gmp/commands/agent-group'; +import {type EntityActionData} from 'gmp/commands/entity'; +import AgentGroup from 'gmp/models/agent-group'; +import useGmp from 'web/hooks/useGmp'; +import useCloneMutation from 'web/queries/useCloneMutation'; +import useCreateMutation from 'web/queries/useCreateMutation'; +import useDeleteMutation from 'web/queries/useDeleteMutation'; +import useSaveMutation from 'web/queries/useSaveMutation'; + +interface UseCreateAgentGroupParams { + onSuccess?: (data: EntityActionData) => void; + onError?: (error: Error) => void; +} + +interface UseModifyAgentGroupParams { + onSuccess?: () => void; + onError?: (error: Error) => void; +} + +export const useCreateAgentGroup = ({ + onSuccess, + onError, +}: UseCreateAgentGroupParams) => { + const gmp = useGmp(); + return useCreateMutation({ + gmpMethod: async data => { + const response = await gmp.agentgroup.create(data); + return response.data; + }, + entityType: AgentGroup.entityType, + invalidateQueryIds: ['get_agent_groups'], + onError, + onSuccess, + }); +}; + +export const useCloneAgentGroup = ({ + onError, + onSuccess, +}: UseCreateAgentGroupParams) => { + const gmp = useGmp(); + return useCloneMutation({ + gmpMethod: async ({id}) => { + const response = await gmp.agentgroup.clone({id}); + return response.data; + }, + entityType: AgentGroup.entityType, + invalidateQueryIds: ['get_agent_groups'], + onError, + onSuccess, + }); +}; + +export const useSaveAgentGroup = ({ + onError, + onSuccess, +}: UseModifyAgentGroupParams) => { + const gmp = useGmp(); + return useSaveMutation({ + gmpMethod: async data => { + await gmp.agentgroup.save(data); + }, + entityType: AgentGroup.entityType, + invalidateQueryIds: ['get_agent_groups'], + onError, + onSuccess, + }); +}; + +export const useDeleteAgentGroup = ({ + onError, + onSuccess, +}: UseModifyAgentGroupParams) => { + const gmp = useGmp(); + return useDeleteMutation({ + gmpMethod: ({id}) => gmp.agentgroup.delete({id}), + entityType: AgentGroup.entityType, + invalidateQueryIds: ['get_agent_groups'], + onSuccess, + onError, + }); +}; diff --git a/src/web/hooks/use-query/agent-groups.ts b/src/web/hooks/use-query/agent-groups.ts index 369c82f6b8..4bf31f5c89 100644 --- a/src/web/hooks/use-query/agent-groups.ts +++ b/src/web/hooks/use-query/agent-groups.ts @@ -3,30 +3,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { - type AgentGroupCreateParams, - type AgentGroupSaveParams, -} from 'gmp/commands/agent-group'; -import {type EntityActionResponse} from 'gmp/commands/entity'; -import type Rejection from 'gmp/http/rejection'; -import AgentGroup from 'gmp/models/agent-group'; +import type AgentGroup from 'gmp/models/agent-group'; import {type FilterType} from 'gmp/models/filter'; import useGmp from 'web/hooks/useGmp'; -import useCloneMutation from 'web/queries/useCloneMutation'; -import useCreateMutation from 'web/queries/useCreateMutation'; -import useDeleteMutation from 'web/queries/useDeleteMutation'; import useGetEntities from 'web/queries/useGetEntities'; -import useSaveMutation from 'web/queries/useSaveMutation'; - -interface UseCreateAgentGroupParams { - onSuccess?: (data: EntityActionResponse) => void; - onError?: (error: Rejection) => void; -} - -interface UseModifyAgentGroupParams { - onSuccess?: () => void; - onError?: (error: Rejection) => void; -} export const useGetAgentGroups = ({filter}: {filter?: FilterType}) => { const gmp = useGmp(); @@ -36,63 +16,3 @@ export const useGetAgentGroups = ({filter}: {filter?: FilterType}) => { gmpMethod: gmp.agentgroups.get.bind(gmp.agentgroups), }); }; - -export const useCreateAgentGroup = ({ - onSuccess, - onError, -}: UseCreateAgentGroupParams) => { - const gmp = useGmp(); - return useCreateMutation< - AgentGroupCreateParams, - EntityActionResponse, - Rejection - >({ - gmpMethod: gmp.agentgroup.create.bind(gmp.agentgroup), - entityType: AgentGroup.entityType, - invalidateQueryIds: ['get_agent_groups'], - onError, - onSuccess, - }); -}; - -export const useCloneAgentGroup = ({ - onError, - onSuccess, -}: UseCreateAgentGroupParams) => { - const gmp = useGmp(); - return useCloneMutation({ - gmpMethod: gmp.agentgroup.clone.bind(gmp.agentgroup), - entityType: AgentGroup.entityType, - invalidateQueryIds: ['get_agent_groups'], - onError, - onSuccess, - }); -}; - -export const useSaveAgentGroup = ({ - onError, - onSuccess, -}: UseModifyAgentGroupParams) => { - const gmp = useGmp(); - return useSaveMutation({ - gmpMethod: gmp.agentgroup.save.bind(gmp.agentgroup), - entityType: AgentGroup.entityType, - invalidateQueryIds: ['get_agent_groups'], - onError, - onSuccess, - }); -}; - -export const useDeleteAgentGroup = ({ - onError, - onSuccess, -}: UseModifyAgentGroupParams) => { - const gmp = useGmp(); - return useDeleteMutation({ - gmpMethod: gmp.agentgroup.delete.bind(gmp.agentgroup), - entityType: AgentGroup.entityType, - invalidateQueryIds: ['get_agent_groups'], - onSuccess, - onError, - }); -}; diff --git a/src/web/hooks/use-query/agent.ts b/src/web/hooks/use-query/agent.ts new file mode 100644 index 0000000000..003ead8055 --- /dev/null +++ b/src/web/hooks/use-query/agent.ts @@ -0,0 +1,85 @@ +/* SPDX-FileCopyrightText: 2025 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {useQueryClient} from '@tanstack/react-query'; +import {type AgentModifyParams} from 'gmp/commands/agent'; +import type Response from 'gmp/http/response'; +import {isArray} from 'gmp/utils/identity'; +import useGmp from 'web/hooks/useGmp'; +import useDeleteMutation from 'web/queries/useDeleteMutation'; +import useGmpMutation from 'web/queries/useGmpMutation'; +import useSaveMutation from 'web/queries/useSaveMutation'; + +interface UseModifyAgentParams { + onSuccess?: () => void; + onError?: (error: Error) => void; +} + +interface UseDownloadAgentSupportBundleParams { + onSuccess?: (response: Response) => void; + onError?: (error: Error) => void; +} + +export interface DownloadAgentSupportBundleInput { + id: string; + encryption?: boolean; +} + +export const useModifyAgent = ({ + onError, + onSuccess, +}: UseModifyAgentParams = {}) => { + const queryClient = useQueryClient(); + const gmp = useGmp(); + + const invalidateAgents = () => + queryClient.invalidateQueries({ + predicate: q => { + const key = q.queryKey as unknown as string[]; + return ( + key?.includes?.('get_agents') || + (isArray(key) && key[0] === 'get_entities' && key.includes('agent')) + ); + }, + }); + + return useSaveMutation({ + entityType: 'agent', + gmpMethod: data => gmp.agent.save(data), + invalidateQueryIds: ['get_agents'], + onSuccess: async () => { + await invalidateAgents(); + onSuccess?.(); + }, + onError, + }); +}; + +export const useDeleteAgent = ({onError, onSuccess}: UseModifyAgentParams) => { + const gmp = useGmp(); + return useDeleteMutation({ + gmpMethod: ({id}) => gmp.agent.delete({id}), + entityType: 'agent', + invalidateQueryIds: ['get_agents'], + onSuccess, + onError, + }); +}; + +export const useDownloadAgentSupportBundle = ({ + onSuccess, + onError, +}: UseDownloadAgentSupportBundleParams = {}) => { + const gmp = useGmp(); + + return useGmpMutation>( + { + gmpMethod: ({id, encryption}) => + gmp.agent.downloadSupportBundle(id, encryption), + onSuccess, + onError, + }, + ); +}; diff --git a/src/web/hooks/use-query/agents.ts b/src/web/hooks/use-query/agents.ts index f9e1e97099..0365ecc09e 100644 --- a/src/web/hooks/use-query/agents.ts +++ b/src/web/hooks/use-query/agents.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {useQueryClient} from '@tanstack/react-query'; -import {type AgentModifyParams} from 'gmp/commands/agent'; import type Rejection from 'gmp/http/rejection'; import type Response from 'gmp/http/response'; import {type XmlMeta} from 'gmp/http/transform/fast-xml'; @@ -13,13 +11,11 @@ import type FilterType from 'gmp/models/filter/filter-type'; import QueryFilter from 'gmp/models/filter/query-filter'; import {isFilterType} from 'gmp/models/filter/utils'; import {parseYesNo} from 'gmp/parser'; -import {isArray, isDefined} from 'gmp/utils/identity'; +import {isDefined} from 'gmp/utils/identity'; import useGmp from 'web/hooks/useGmp'; import useTranslation from 'web/hooks/useTranslation'; -import useDeleteMutation from 'web/queries/useDeleteMutation'; import useGetEntities from 'web/queries/useGetEntities'; import useGmpMutation from 'web/queries/useGmpMutation'; -import useSaveMutation from 'web/queries/useSaveMutation'; interface UseGetAgentsParams { filter?: FilterType; @@ -30,21 +26,11 @@ interface UseGetAgentsParams { interface UseModifyAgentParams { onSuccess?: () => void; - onError?: (error: Rejection) => void; + onError?: (error: Error) => void; } type AgentBulkInput = Agent[] | FilterType; -interface UseDownloadAgentSupportBundleParams { - onSuccess?: (response: Response) => void; - onError?: (error: Rejection) => void; -} - -export interface DownloadAgentSupportBundleInput { - id: string; - encryption?: boolean; -} - export const useGetAgents = ({ filter, scannerId, @@ -75,47 +61,6 @@ export const useGetAgents = ({ }); }; -export const useModifyAgent = ({ - onError, - onSuccess, -}: UseModifyAgentParams = {}) => { - const queryClient = useQueryClient(); - const gmp = useGmp(); - - const invalidateAgents = () => - queryClient.invalidateQueries({ - predicate: q => { - const key = q.queryKey as unknown as string[]; - return ( - key?.includes?.('get_agents') || - (isArray(key) && key[0] === 'get_entities' && key.includes('agent')) - ); - }, - }); - - return useSaveMutation({ - entityType: 'agent', - gmpMethod: gmp.agent.save.bind(gmp.agent), - invalidateQueryIds: ['get_agents'], - onSuccess: async () => { - await invalidateAgents(); - onSuccess?.(); - }, - onError, - }); -}; - -export const useDeleteAgent = ({onError, onSuccess}: UseModifyAgentParams) => { - const gmp = useGmp(); - return useDeleteMutation({ - gmpMethod: gmp.agent.delete.bind(gmp.agentgroup), - entityType: 'agentgroup', - invalidateQueryIds: ['get_agents'], - onSuccess, - onError, - }); -}; - export const useBulkDeleteAgents = ({ onError, onSuccess, @@ -179,7 +124,7 @@ export const useSyncAgents = ({ }: UseModifyAgentParams = {}) => { const [_] = useTranslation(); const gmp = useGmp(); - return useGmpMutation({ + return useGmpMutation({ gmpMethod: () => gmp.agents.sync(), invalidateQueryIds: ['get_agents'], successMessage: _('Agents successfully synced'), @@ -229,21 +174,3 @@ export const useBulkDisableUpdateToLatestAgents = ({ onError, }); }; - -export const useDownloadAgentSupportBundle = ({ - onSuccess, - onError, -}: UseDownloadAgentSupportBundleParams = {}) => { - const gmp = useGmp(); - - return useGmpMutation< - DownloadAgentSupportBundleInput, - Response, - Rejection - >({ - gmpMethod: ({id, encryption}) => - gmp.agent.downloadSupportBundle(id, encryption), - onSuccess, - onError, - }); -}; diff --git a/src/web/hooks/use-query/audit.ts b/src/web/hooks/use-query/audit.ts new file mode 100644 index 0000000000..e190402d06 --- /dev/null +++ b/src/web/hooks/use-query/audit.ts @@ -0,0 +1,24 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type Audit from 'gmp/models/audit'; +import useGmp from 'web/hooks/useGmp'; +import {type RefetchIntervalFn} from 'web/queries/helpers'; +import useGetEntity from 'web/queries/useGetEntity'; + +interface UseGetAuditParams { + id: string; + refetchInterval?: RefetchIntervalFn; +} + +export const useGetAudit = ({id, refetchInterval}: UseGetAuditParams) => { + const gmp = useGmp(); + return useGetEntity({ + gmpMethod: gmp.audit.get.bind(gmp.audit), + queryId: 'get_audit', + id, + refetchInterval, + }); +}; diff --git a/src/web/hooks/use-query/audits.ts b/src/web/hooks/use-query/audits.ts index 1cabb9b8d9..47bfd1002f 100644 --- a/src/web/hooks/use-query/audits.ts +++ b/src/web/hooks/use-query/audits.ts @@ -6,29 +6,12 @@ import type Audit from 'gmp/models/audit'; import {type FilterType} from 'gmp/models/filter'; import useGmp from 'web/hooks/useGmp'; -import {type RefetchIntervalFn} from 'web/queries/helpers'; import useGetEntities from 'web/queries/useGetEntities'; -import useGetEntity from 'web/queries/useGetEntity'; - -interface UseGetAuditParams { - id: string; - refetchInterval?: RefetchIntervalFn; -} interface UseGetAuditsParams { filter?: FilterType; } -export const useGetAudit = ({id, refetchInterval}: UseGetAuditParams) => { - const gmp = useGmp(); - return useGetEntity({ - gmpMethod: gmp.audit.get.bind(gmp.audit), - queryId: 'get_audit', - id, - refetchInterval, - }); -}; - export const useGetAudits = ({filter}: UseGetAuditsParams = {}) => { const gmp = useGmp(); return useGetEntities({ diff --git a/src/web/hooks/use-query/credential-store.ts b/src/web/hooks/use-query/credential-store.ts index 8d6511b8f8..a117459f31 100644 --- a/src/web/hooks/use-query/credential-store.ts +++ b/src/web/hooks/use-query/credential-store.ts @@ -3,13 +3,11 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {type CredentialStoreModifyParams} from 'gmp/commands/credential-store'; -import {type EntityActionResponse} from 'gmp/commands/entity'; -import type Rejection from 'gmp/http/rejection'; -import type CredentialStore from 'gmp/models/credential-store'; -import {type FilterType} from 'gmp/models/filter'; +import { + type CredentialStoreVerifyParams, + type CredentialStoreModifyParams, +} from 'gmp/commands/credential-store'; import useGmp from 'web/hooks/useGmp'; -import useGetEntities from 'web/queries/useGetEntities'; import useGmpMutation from 'web/queries/useGmpMutation'; interface UseEditCredentialStoreParams { @@ -22,27 +20,16 @@ interface UseVerifyCredentialStoreParams { onError?: (error: Error) => void; } -export const useGetCredentialStores = ({filter}: {filter?: FilterType}) => { - const gmp = useGmp(); - return useGetEntities({ - queryId: 'get_credential_stores', - filter, - gmpMethod: gmp.credentialstores.get.bind(gmp.credentialstores), - }); -}; - export const useEditCredentialStore = ({ onError, onSuccess, }: UseEditCredentialStoreParams) => { const gmp = useGmp(); - - return useGmpMutation< - CredentialStoreModifyParams, - EntityActionResponse, - Rejection - >({ - gmpMethod: gmp.credentialstore.edit.bind(gmp.credentialstore), + return useGmpMutation({ + gmpMethod: async data => { + const response = await gmp.credentialstore.edit(data); + return response.data; + }, invalidateQueryIds: ['get_credential_stores'], onError, onSuccess, @@ -55,8 +42,11 @@ export const useVerifyCredentialStore = ({ }: UseVerifyCredentialStoreParams) => { const gmp = useGmp(); - return useGmpMutation<{id: string}, EntityActionResponse, Rejection>({ - gmpMethod: gmp.credentialstore.verify.bind(gmp.credentialstore), + return useGmpMutation({ + gmpMethod: async ({id}) => { + const response = await gmp.credentialstore.verify({id}); + return response.data; + }, onError, onSuccess, }); diff --git a/src/web/hooks/use-query/credential-stores.ts b/src/web/hooks/use-query/credential-stores.ts new file mode 100644 index 0000000000..35929c11cf --- /dev/null +++ b/src/web/hooks/use-query/credential-stores.ts @@ -0,0 +1,18 @@ +/* SPDX-FileCopyrightText: 2025 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type CredentialStore from 'gmp/models/credential-store'; +import {type FilterType} from 'gmp/models/filter'; +import useGmp from 'web/hooks/useGmp'; +import useGetEntities from 'web/queries/useGetEntities'; + +export const useGetCredentialStores = ({filter}: {filter?: FilterType}) => { + const gmp = useGmp(); + return useGetEntities({ + queryId: 'get_credential_stores', + filter, + gmpMethod: gmp.credentialstores.get.bind(gmp.credentialstores), + }); +}; diff --git a/src/web/hooks/use-query/credential.ts b/src/web/hooks/use-query/credential.ts new file mode 100644 index 0000000000..fc0ff626ac --- /dev/null +++ b/src/web/hooks/use-query/credential.ts @@ -0,0 +1,35 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type CredentialCommandCreateParams} from 'gmp/commands/credential'; +import {type EntityActionData} from 'gmp/commands/entity'; +import useGmp from 'web/hooks/useGmp'; +import useCreateMutation from 'web/queries/useCreateMutation'; + +interface UseCredentialMutationCallbacks { + onSuccess?: (response: TResponse) => void; + onError?: (error: Error) => void; +} + +export const useCreateCredential = ({ + onSuccess, + onError, +}: UseCredentialMutationCallbacks = {}) => { + const gmp = useGmp(); + return useCreateMutation< + CredentialCommandCreateParams, + EntityActionData, + Error + >({ + gmpMethod: async data => { + const response = await gmp.credential.create(data); + return response.data; + }, + entityType: 'credential', + invalidateQueryIds: ['get_credentials'], + onSuccess, + onError, + }); +}; diff --git a/src/web/hooks/use-query/credentials.ts b/src/web/hooks/use-query/credentials.ts new file mode 100644 index 0000000000..18e722174e --- /dev/null +++ b/src/web/hooks/use-query/credentials.ts @@ -0,0 +1,45 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type Credential from 'gmp/models/credential'; +import {type FilterType} from 'gmp/models/filter'; +import useGmp from 'web/hooks/useGmp'; +import useGetEntities from 'web/queries/useGetEntities'; + +interface UseGetCredentialsParams { + enabled?: boolean; + filter?: FilterType; + staleTime?: number; +} + +export const useGetCredentials = ({ + enabled, + filter, + staleTime, +}: UseGetCredentialsParams = {}) => { + const gmp = useGmp(); + return useGetEntities({ + gmpMethod: gmp.credentials.get.bind(gmp.credentials), + queryId: 'get_credentials', + enabled, + filter, + staleTime, + }); +}; + +export const useGetAllCredentials = ({ + enabled, + filter, + staleTime, +}: UseGetCredentialsParams = {}) => { + const gmp = useGmp(); + return useGetEntities({ + gmpMethod: gmp.credentials.getAll.bind(gmp.credentials), + queryId: 'get_credentials', + enabled, + filter, + staleTime, + }); +}; diff --git a/src/web/hooks/use-query/oci-image-target.ts b/src/web/hooks/use-query/oci-image-target.ts new file mode 100644 index 0000000000..7badd820e0 --- /dev/null +++ b/src/web/hooks/use-query/oci-image-target.ts @@ -0,0 +1,93 @@ +/* SPDX-FileCopyrightText: 2025 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type EntityActionData} from 'gmp/commands/entity'; +import { + type OciImageTargetCreateParams, + type OciImageTargetSaveParams, +} from 'gmp/commands/oci-image-target'; +import useGmp from 'web/hooks/useGmp'; +import useCloneMutation from 'web/queries/useCloneMutation'; +import useCreateMutation from 'web/queries/useCreateMutation'; +import useMoveToTrashCan from 'web/queries/useMoveToTrashCan'; +import useSaveMutation from 'web/queries/useSaveMutation'; + +interface UseCreateOciImageTargetParams { + onSuccess?: (data: EntityActionData) => void; + onError?: (error: Error) => void; +} + +interface UseModifyOciImageTargetParams { + onSuccess?: () => void; + onError?: (error: Error) => void; +} + +export const useCreateOciImageTarget = ({ + onSuccess, + onError, +}: UseCreateOciImageTargetParams) => { + const gmp = useGmp(); + + return useCreateMutation({ + gmpMethod: async data => { + const response = await gmp.ociimagetarget.create(data); + return response.data; + }, + entityType: 'ociimagetarget', + invalidateQueryIds: ['get_oci_image_targets'], + onError, + onSuccess, + }); +}; + +export const useSaveOciImageTarget = ({ + onError, + onSuccess, +}: UseModifyOciImageTargetParams) => { + const gmp = useGmp(); + + return useSaveMutation({ + gmpMethod: async data => { + await gmp.ociimagetarget.save(data); + }, + entityType: 'ociimagetarget', + invalidateQueryIds: ['get_oci_image_targets'], + onError, + onSuccess, + }); +}; + +export const useDeleteOciImageTarget = ({ + onError, + onSuccess, +}: UseModifyOciImageTargetParams) => { + const gmp = useGmp(); + + return useMoveToTrashCan({ + gmpMethod: ({id}) => gmp.ociimagetarget.delete({id}), + entityType: 'ociimagetarget', + invalidateQueryIds: ['get_oci_image_targets'], + onSuccess, + onError, + }); +}; + +export const useCloneOciImageTarget = ({ + onError, + onSuccess, +}: UseCreateOciImageTargetParams) => { + const gmp = useGmp(); + + return useCloneMutation({ + gmpMethod: async ({id}) => { + const response = await gmp.ociimagetarget.clone({id}); + return response.data; + }, + entityType: 'ociimagetarget', + invalidateQueryIds: ['get_oci_image_targets'], + onError, + onSuccess, + }); +}; diff --git a/src/web/hooks/use-query/oci-image-targets.ts b/src/web/hooks/use-query/oci-image-targets.ts index 4eaf715536..cefd233408 100644 --- a/src/web/hooks/use-query/oci-image-targets.ts +++ b/src/web/hooks/use-query/oci-image-targets.ts @@ -3,11 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {type EntityActionResponse} from 'gmp/commands/entity'; -import { - type OciImageTargetCreateParams, - type OciImageTargetSaveParams, -} from 'gmp/commands/oci-image-target'; import type Rejection from 'gmp/http/rejection'; import type Response from 'gmp/http/response'; import {type XmlMeta} from 'gmp/http/transform/fast-xml'; @@ -16,23 +11,14 @@ import {isFilterType} from 'gmp/models/filter/utils'; import type OciImageTarget from 'gmp/models/oci-image-target'; import useGmp from 'web/hooks/useGmp'; import useTranslation from 'web/hooks/useTranslation'; -import useCloneMutation from 'web/queries/useCloneMutation'; -import useCreateMutation from 'web/queries/useCreateMutation'; import useGetEntities from 'web/queries/useGetEntities'; import useGmpMutation from 'web/queries/useGmpMutation'; -import useMoveToTrashCan from 'web/queries/useMoveToTrashCan'; -import useSaveMutation from 'web/queries/useSaveMutation'; type OciImageTargetBulkInput = OciImageTarget[] | FilterType; -interface UseCreateOciImageTargetParams { - onSuccess?: (data: EntityActionResponse) => void; - onError?: (error: Error) => void; -} - interface UseModifyOciImageTargetParams { - onSuccess?: () => void; onError?: (error: Error) => void; + onSuccess?: () => void; } export const useGetOciImageTargets = ({filter}: {filter?: FilterType}) => { @@ -45,74 +31,6 @@ export const useGetOciImageTargets = ({filter}: {filter?: FilterType}) => { }); }; -export const useCreateOciImageTarget = ({ - onSuccess, - onError, -}: UseCreateOciImageTargetParams) => { - const gmp = useGmp(); - - return useCreateMutation< - OciImageTargetCreateParams, - EntityActionResponse, - Rejection - >({ - gmpMethod: gmp.ociimagetarget.create.bind(gmp.ociimagetarget), - entityType: 'ociimagetarget', - invalidateQueryIds: ['get_oci_image_targets'], - onError, - onSuccess, - }); -}; - -export const useSaveOciImageTarget = ({ - onError, - onSuccess, -}: UseModifyOciImageTargetParams) => { - const gmp = useGmp(); - - return useSaveMutation< - OciImageTargetSaveParams, - EntityActionResponse, - Rejection - >({ - gmpMethod: gmp.ociimagetarget.save.bind(gmp.ociimagetarget), - entityType: 'ociimagetarget', - invalidateQueryIds: ['get_oci_image_targets'], - onError, - onSuccess, - }); -}; - -export const useDeleteOciImageTarget = ({ - onError, - onSuccess, -}: UseModifyOciImageTargetParams) => { - const gmp = useGmp(); - - return useMoveToTrashCan({ - gmpMethod: gmp.ociimagetarget.delete.bind(gmp.ociimagetarget), - entityType: 'ociimagetarget', - invalidateQueryIds: ['get_oci_image_targets'], - onSuccess, - onError, - }); -}; - -export const useCloneOciImageTarget = ({ - onError, - onSuccess, -}: UseCreateOciImageTargetParams) => { - const gmp = useGmp(); - - return useCloneMutation({ - gmpMethod: gmp.ociimagetarget.clone.bind(gmp.ociimagetarget), - entityType: 'ociimagetarget', - invalidateQueryIds: ['get_oci_image_targets'], - onError, - onSuccess, - }); -}; - export const useBulkDeleteOciImageTargets = ({ onError, onSuccess, diff --git a/src/web/hooks/use-query/policies.ts b/src/web/hooks/use-query/policies.ts index b4330279f4..d1ad063a96 100644 --- a/src/web/hooks/use-query/policies.ts +++ b/src/web/hooks/use-query/policies.ts @@ -7,25 +7,11 @@ import {type FilterType} from 'gmp/models/filter'; import type Policy from 'gmp/models/policy'; import useGmp from 'web/hooks/useGmp'; import useGetEntities from 'web/queries/useGetEntities'; -import useGetEntity from 'web/queries/useGetEntity'; - -interface UseGetPolicyParams { - id: string; -} interface UseGetPoliciesParams { filter?: FilterType; } -export const useGetPolicy = ({id}: UseGetPolicyParams) => { - const gmp = useGmp(); - return useGetEntity({ - gmpMethod: gmp.policy.get.bind(gmp.policy), - queryId: 'get_policy', - id, - }); -}; - export const useGetPolicies = ({filter}: UseGetPoliciesParams = {}) => { const gmp = useGmp(); return useGetEntities({ diff --git a/src/web/hooks/use-query/policy.ts b/src/web/hooks/use-query/policy.ts new file mode 100644 index 0000000000..ee472bd0ed --- /dev/null +++ b/src/web/hooks/use-query/policy.ts @@ -0,0 +1,21 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type Policy from 'gmp/models/policy'; +import useGmp from 'web/hooks/useGmp'; +import useGetEntity from 'web/queries/useGetEntity'; + +interface UseGetPolicyParams { + id: string; +} + +export const useGetPolicy = ({id}: UseGetPolicyParams) => { + const gmp = useGmp(); + return useGetEntity({ + gmpMethod: gmp.policy.get.bind(gmp.policy), + queryId: 'get_policy', + id, + }); +}; diff --git a/src/web/hooks/use-query/port-list.ts b/src/web/hooks/use-query/port-list.ts new file mode 100644 index 0000000000..2fbcd3ddc8 --- /dev/null +++ b/src/web/hooks/use-query/port-list.ts @@ -0,0 +1,149 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type EntityActionData} from 'gmp/commands/entity'; +import { + type PortListCommandImportParams, + type PortListCommandCreateParams, + type PortListCommandSaveParams, + type PortListCommandCreatePortRangeParams, + type PortListCommandDeletePortRangeParams, +} from 'gmp/commands/port-list'; +import _ from 'gmp/locale'; +import useGmp from 'web/hooks/useGmp'; +import useCloneMutation from 'web/queries/useCloneMutation'; +import useCreateMutation from 'web/queries/useCreateMutation'; +import useDeleteMutation from 'web/queries/useDeleteMutation'; +import useGmpMutation from 'web/queries/useGmpMutation'; + +interface UsePortListMutationCallbacks { + onSuccess?: (response: TResponse) => void; + onError?: (error: Error) => void; +} + +interface UseGetPortListParams { + id: string; +} + +interface UseModifyPortListParams extends UseGetPortListParams { + name?: string; +} + +export const useCreatePortList = ({ + onSuccess, + onError, +}: UsePortListMutationCallbacks = {}) => { + const gmp = useGmp(); + return useCreateMutation< + PortListCommandCreateParams, + EntityActionData, + Error + >({ + gmpMethod: async data => { + const response = await gmp.portlist.create(data); + return response.data; + }, + entityType: 'portlist', + invalidateQueryIds: ['get_port_lists'], + onSuccess, + onError, + }); +}; + +export const useSavePortList = ({ + onSuccess, + onError, +}: UsePortListMutationCallbacks = {}) => { + const gmp = useGmp(); + return useGmpMutation({ + gmpMethod: async data => { + const response = await gmp.portlist.save(data); + return response.data; + }, + invalidateQueryIds: ['get_port_lists', 'get_port_list'], + onSuccess, + onError, + }); +}; + +export const useClonePortList = ({ + onSuccess, + onError, +}: UsePortListMutationCallbacks = {}) => { + const gmp = useGmp(); + return useCloneMutation({ + gmpMethod: async ({id}: UseModifyPortListParams) => { + const response = await gmp.portlist.clone({id}); + return response.data; + }, + entityType: 'portlist', + invalidateQueryIds: ['get_port_lists'], + onSuccess, + onError, + }); +}; + +export const useDeletePortList = ({ + onSuccess, + onError, +}: UsePortListMutationCallbacks = {}) => { + const gmp = useGmp(); + return useDeleteMutation({ + gmpMethod: ({id}) => gmp.portlist.delete({id}), + entityType: 'portlist', + invalidateQueryIds: ['get_port_lists', 'get_port_list'], + onSuccess, + onError, + }); +}; + +export const useImportPortList = ({ + onSuccess, + onError, +}: UsePortListMutationCallbacks = {}) => { + const gmp = useGmp(); + return useGmpMutation({ + gmpMethod: async data => { + const response = await gmp.portlist.import(data); + return response.data; + }, + successMessage: _('Port List imported successfully.'), + invalidateQueryIds: ['get_port_lists'], + onSuccess, + onError, + }); +}; + +export const useCreatePortRange = ({ + onSuccess, + onError, +}: UsePortListMutationCallbacks = {}) => { + const gmp = useGmp(); + return useGmpMutation( + { + gmpMethod: async data => { + const response = await gmp.portlist.createPortRange(data); + return response.data; + }, + invalidateQueryIds: ['get_port_lists', 'get_port_list'], + onSuccess, + onError, + }, + ); +}; + +export const useDeletePortRange = ({ + onSuccess, + onError, +}: UsePortListMutationCallbacks = {}) => { + const gmp = useGmp(); + return useGmpMutation({ + gmpMethod: async ({id}: PortListCommandDeletePortRangeParams) => + gmp.portlist.deletePortRange({id}), + invalidateQueryIds: ['get_port_lists', 'get_port_list'], + onSuccess, + onError, + }); +}; diff --git a/src/web/hooks/use-query/port-lists.ts b/src/web/hooks/use-query/port-lists.ts new file mode 100644 index 0000000000..e4178ca36f --- /dev/null +++ b/src/web/hooks/use-query/port-lists.ts @@ -0,0 +1,45 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type FilterType} from 'gmp/models/filter'; +import type PortList from 'gmp/models/port-list'; +import useGmp from 'web/hooks/useGmp'; +import useGetEntities from 'web/queries/useGetEntities'; + +interface UseGetPortListsParams { + enabled?: boolean; + filter?: FilterType; + staleTime?: number; +} + +export const useGetPortLists = ({ + enabled, + filter, + staleTime, +}: UseGetPortListsParams = {}) => { + const gmp = useGmp(); + return useGetEntities({ + gmpMethod: gmp.portlists.get.bind(gmp.portlists), + queryId: 'get_port_lists', + enabled, + filter, + staleTime, + }); +}; + +export const useGetAllPortLists = ({ + enabled, + filter, + staleTime, +}: UseGetPortListsParams = {}) => { + const gmp = useGmp(); + return useGetEntities({ + gmpMethod: gmp.portlists.getAll.bind(gmp.portlists), + queryId: 'get_port_lists', + enabled, + filter, + staleTime, + }); +}; diff --git a/src/web/hooks/use-query/tag.ts b/src/web/hooks/use-query/tag.ts new file mode 100644 index 0000000000..129a76037e --- /dev/null +++ b/src/web/hooks/use-query/tag.ts @@ -0,0 +1,134 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type EntityActionResponse} from 'gmp/commands/entity'; +import type Rejection from 'gmp/http/rejection'; +import type Response from 'gmp/http/response'; +import {type XmlMeta, type XmlResponseData} from 'gmp/http/transform/fast-xml'; +import type Tag from 'gmp/models/tag'; +import useGmp from 'web/hooks/useGmp'; +import {type RefetchIntervalFn} from 'web/queries/helpers'; +import useCloneMutation from 'web/queries/useCloneMutation'; +import useCreateMutation from 'web/queries/useCreateMutation'; +import useDeleteMutation from 'web/queries/useDeleteMutation'; +import useGetEntity from 'web/queries/useGetEntity'; +import useGmpMutation from 'web/queries/useGmpMutation'; +import useSaveMutation from 'web/queries/useSaveMutation'; + +interface UseGetTagParams { + id: string; + refetchInterval?: RefetchIntervalFn; +} + +interface UseMutationCallbacks { + onSuccess?: () => void; + onError?: (error: Rejection) => void; +} + +interface UseCreateTagParams { + onSuccess?: (data: EntityActionResponse) => void; + onError?: (error: Error) => void; +} + +interface UseModifyTagParams { + onSuccess?: () => void; + onError?: (error: Error) => void; +} + +export const useGetTag = ({id, refetchInterval}: UseGetTagParams) => { + const gmp = useGmp(); + return useGetEntity({ + gmpMethod: gmp.tag.get.bind(gmp.tag), + queryId: 'get_tag', + id, + refetchInterval, + }); +}; + +export const useDeleteTag = ({onError, onSuccess}: UseMutationCallbacks) => { + const gmp = useGmp(); + return useDeleteMutation({ + entityType: 'tag', + gmpMethod: ({id}) => gmp.tag.delete({id}), + invalidateQueryIds: ['get_tags'], + onSuccess, + onError, + }); +}; + +export const useEnableTag = ({ + onError, + onSuccess, +}: UseMutationCallbacks = {}) => { + const gmp = useGmp(); + return useGmpMutation< + {id: string}, + Response, + Rejection + >({ + gmpMethod: ({id}) => gmp.tag.enable({id}), + invalidateQueryIds: ['get_tags'], + onSuccess, + onError, + }); +}; + +export const useDisableTag = ({ + onError, + onSuccess, +}: UseMutationCallbacks = {}) => { + const gmp = useGmp(); + return useGmpMutation< + {id: string}, + Response, + Rejection + >({ + gmpMethod: ({id}) => gmp.tag.disable({id}), + invalidateQueryIds: ['get_tags'], + onSuccess, + onError, + }); +}; + +export const useCreateTag = ({onSuccess, onError}: UseCreateTagParams) => { + const gmp = useGmp(); + return useCreateMutation< + Parameters[0], + EntityActionResponse, + Rejection + >({ + entityType: 'tag', + gmpMethod: input => gmp.tag.create(input), + invalidateQueryIds: ['get_tags'], + onError, + onSuccess, + }); +}; + +export const useSaveTag = ({onError, onSuccess}: UseModifyTagParams) => { + const gmp = useGmp(); + return useSaveMutation< + Parameters[0], + EntityActionResponse, + Rejection + >({ + entityType: 'tag', + gmpMethod: input => gmp.tag.save(input), + invalidateQueryIds: ['get_tags'], + onError, + onSuccess, + }); +}; + +export const useCloneTag = ({onSuccess, onError}: UseCreateTagParams) => { + const gmp = useGmp(); + return useCloneMutation({ + entityType: 'tag', + gmpMethod: ({id}) => gmp.tag.clone({id}), + invalidateQueryIds: ['get_tags'], + onError, + onSuccess, + }); +}; diff --git a/src/web/hooks/use-query/tags.ts b/src/web/hooks/use-query/tags.ts index 537435c98a..5f783247a1 100644 --- a/src/web/hooks/use-query/tags.ts +++ b/src/web/hooks/use-query/tags.ts @@ -3,27 +3,15 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {type EntityActionResponse} from 'gmp/commands/entity'; import type Rejection from 'gmp/http/rejection'; import type Response from 'gmp/http/response'; -import {type XmlMeta, type XmlResponseData} from 'gmp/http/transform/fast-xml'; +import {type XmlMeta} from 'gmp/http/transform/fast-xml'; import {type FilterType} from 'gmp/models/filter'; import {isFilterType} from 'gmp/models/filter/utils'; import type Tag from 'gmp/models/tag'; import useGmp from 'web/hooks/useGmp'; -import {type RefetchIntervalFn} from 'web/queries/helpers'; -import useCloneMutation from 'web/queries/useCloneMutation'; -import useCreateMutation from 'web/queries/useCreateMutation'; -import useDeleteMutation from 'web/queries/useDeleteMutation'; import useGetEntities from 'web/queries/useGetEntities'; -import useGetEntity from 'web/queries/useGetEntity'; import useGmpMutation from 'web/queries/useGmpMutation'; -import useSaveMutation from 'web/queries/useSaveMutation'; - -interface UseGetTagParams { - id: string; - refetchInterval?: RefetchIntervalFn; -} interface UseGetTagsParams { filter?: FilterType; @@ -34,16 +22,6 @@ interface UseMutationCallbacks { onError?: (error: Rejection) => void; } -interface UseCreateTagParams { - onSuccess?: (data: EntityActionResponse) => void; - onError?: (error: Error) => void; -} - -interface UseModifyTagParams { - onSuccess?: () => void; - onError?: (error: Error) => void; -} - type TagBulkInput = Tag[] | FilterType; export const useGetTags = ({filter}: UseGetTagsParams) => { @@ -55,61 +33,6 @@ export const useGetTags = ({filter}: UseGetTagsParams) => { }); }; -export const useGetTag = ({id, refetchInterval}: UseGetTagParams) => { - const gmp = useGmp(); - return useGetEntity({ - gmpMethod: gmp.tag.get.bind(gmp.tag), - queryId: 'get_tag', - id, - refetchInterval, - }); -}; - -export const useDeleteTag = ({onError, onSuccess}: UseMutationCallbacks) => { - const gmp = useGmp(); - return useDeleteMutation({ - entityType: 'tag', - gmpMethod: ({id}) => gmp.tag.delete({id}), - invalidateQueryIds: ['get_tags'], - onSuccess, - onError, - }); -}; - -export const useEnableTag = ({ - onError, - onSuccess, -}: UseMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation< - {id: string}, - Response, - Rejection - >({ - gmpMethod: ({id}) => gmp.tag.enable({id}), - invalidateQueryIds: ['get_tags'], - onSuccess, - onError, - }); -}; - -export const useDisableTag = ({ - onError, - onSuccess, -}: UseMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation< - {id: string}, - Response, - Rejection - >({ - gmpMethod: ({id}) => gmp.tag.disable({id}), - invalidateQueryIds: ['get_tags'], - onSuccess, - onError, - }); -}; - export const useBulkDeleteTags = ({ onError, onSuccess, @@ -142,44 +65,3 @@ export const useBulkExportTags = ({ onError, }); }; - -export const useCreateTag = ({onSuccess, onError}: UseCreateTagParams) => { - const gmp = useGmp(); - return useCreateMutation< - Parameters[0], - EntityActionResponse, - Rejection - >({ - entityType: 'tag', - gmpMethod: input => gmp.tag.create(input), - invalidateQueryIds: ['get_tags'], - onError, - onSuccess, - }); -}; - -export const useSaveTag = ({onError, onSuccess}: UseModifyTagParams) => { - const gmp = useGmp(); - return useSaveMutation< - Parameters[0], - EntityActionResponse, - Rejection - >({ - entityType: 'tag', - gmpMethod: input => gmp.tag.save(input), - invalidateQueryIds: ['get_tags'], - onError, - onSuccess, - }); -}; - -export const useCloneTag = ({onSuccess, onError}: UseCreateTagParams) => { - const gmp = useGmp(); - return useCloneMutation({ - entityType: 'tag', - gmpMethod: ({id}) => gmp.tag.clone({id}), - invalidateQueryIds: ['get_tags'], - onError, - onSuccess, - }); -}; diff --git a/src/web/hooks/use-query/target.ts b/src/web/hooks/use-query/target.ts new file mode 100644 index 0000000000..a5415a743a --- /dev/null +++ b/src/web/hooks/use-query/target.ts @@ -0,0 +1,99 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type EntityActionData} from 'gmp/commands/entity'; +import { + type TargetCommandCreateParams, + type TargetCommandSaveParams, +} from 'gmp/commands/target'; +import type Target from 'gmp/models/target'; +import useGmp from 'web/hooks/useGmp'; +import useCloneMutation from 'web/queries/useCloneMutation'; +import useCreateMutation from 'web/queries/useCreateMutation'; +import useDeleteMutation from 'web/queries/useDeleteMutation'; +import useGetEntity from 'web/queries/useGetEntity'; +import useGmpMutation from 'web/queries/useGmpMutation'; + +interface UseTargetMutationCallbacks { + onSuccess?: (response: TResponse) => void; + onError?: (error: Error) => void; +} + +interface UseGetTargetParams { + id: string; +} + +export const useGetTarget = ({id}: UseGetTargetParams) => { + const gmp = useGmp(); + return useGetEntity({ + gmpMethod: (data: UseGetTargetParams) => gmp.target.get(data), + queryId: 'get_targets', + id, + }); +}; + +export const useCreateTarget = ({ + onSuccess, + onError, +}: UseTargetMutationCallbacks = {}) => { + const gmp = useGmp(); + return useCreateMutation({ + gmpMethod: async data => { + const response = await gmp.target.create(data); + return response.data; + }, + entityType: 'target', + invalidateQueryIds: ['get_targets'], + onSuccess, + onError, + }); +}; + +export const useSaveTarget = ({ + onSuccess, + onError, +}: UseTargetMutationCallbacks = {}) => { + const gmp = useGmp(); + return useGmpMutation({ + gmpMethod: async data => { + const response = await gmp.target.save(data); + return response.data; + }, + invalidateQueryIds: ['get_targets', 'get_target'], + onSuccess, + onError, + }); +}; + +export const useCloneTarget = ({ + onSuccess, + onError, +}: UseTargetMutationCallbacks = {}) => { + const gmp = useGmp(); + return useCloneMutation({ + gmpMethod: async ({id}) => { + const response = await gmp.target.clone({id}); + return response.data; + }, + entityType: 'target', + invalidateQueryIds: ['get_targets'], + onSuccess, + onError, + }); +}; + +export const useDeleteTarget = ({ + onSuccess, + onError, +}: UseTargetMutationCallbacks = {}) => { + const gmp = useGmp(); + return useDeleteMutation({ + gmpMethod: ({id}) => gmp.target.delete({id}), + entityType: 'target', + invalidateQueryIds: ['get_targets', 'get_target'], + onSuccess, + onError, + }); +}; diff --git a/src/web/hooks/use-query/targets.ts b/src/web/hooks/use-query/targets.ts new file mode 100644 index 0000000000..b227b64fb2 --- /dev/null +++ b/src/web/hooks/use-query/targets.ts @@ -0,0 +1,45 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type FilterType} from 'gmp/models/filter'; +import type Target from 'gmp/models/target'; +import useGmp from 'web/hooks/useGmp'; +import useGetEntities from 'web/queries/useGetEntities'; + +interface UseGetTasksParams { + enabled?: boolean; + filter?: FilterType; + staleTime?: number; +} + +export const useGetTargets = ({ + enabled, + filter, + staleTime, +}: UseGetTasksParams = {}) => { + const gmp = useGmp(); + return useGetEntities({ + gmpMethod: gmp.targets.get.bind(gmp.targets), + queryId: 'get_targets', + enabled, + filter, + staleTime, + }); +}; + +export const useGetAllTargets = ({ + enabled, + filter, + staleTime, +}: UseGetTasksParams = {}) => { + const gmp = useGmp(); + return useGetEntities({ + gmpMethod: gmp.targets.getAll.bind(gmp.targets), + queryId: 'get_targets', + enabled, + filter, + staleTime, + }); +}; diff --git a/src/web/hooks/use-query/ticket.ts b/src/web/hooks/use-query/ticket.ts new file mode 100644 index 0000000000..e2506140ce --- /dev/null +++ b/src/web/hooks/use-query/ticket.ts @@ -0,0 +1,126 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type EntityActionData} from 'gmp/commands/entity'; +import type Response from 'gmp/http/response'; +import { + type default as Ticket, + type TicketStatusValue, +} from 'gmp/models/ticket'; +import useGmp from 'web/hooks/useGmp'; +import useCloneMutation from 'web/queries/useCloneMutation'; +import useCreateMutation from 'web/queries/useCreateMutation'; +import useDeleteMutation from 'web/queries/useDeleteMutation'; +import useGetEntity from 'web/queries/useGetEntity'; +import useGmpMutation from 'web/queries/useGmpMutation'; + +interface UseGetTicketParams { + id: string; +} + +interface UseTicketMutationCallbacks { + onSuccess?: (response: TResponse) => void; + onError?: (error: Error) => void; +} + +interface TicketCreateInput { + resultId: string; + userId: string; + note?: string; +} + +interface TicketSaveInput { + id: string; + openNote?: string; + fixedNote?: string; + closedNote?: string; + status: TicketStatusValue; + userId: string; +} + +export const useGetTicket = ({id}: UseGetTicketParams) => { + const gmp = useGmp(); + return useGetEntity({ + gmpMethod: gmp.ticket.get.bind(gmp.ticket), + queryId: 'get_ticket', + id, + }); +}; + +export const useCreateTicket = ({ + onSuccess, + onError, +}: UseTicketMutationCallbacks = {}) => { + const gmp = useGmp(); + return useCreateMutation({ + gmpMethod: async data => { + const response = await gmp.ticket.create(data); + return response.data; + }, + entityType: 'ticket', + invalidateQueryIds: ['get_tickets'], + onSuccess, + onError, + }); +}; + +export const useSaveTicket = ({ + onSuccess, + onError, +}: UseTicketMutationCallbacks = {}) => { + const gmp = useGmp(); + return useGmpMutation({ + gmpMethod: async data => { + const response = await gmp.ticket.save(data); + return response.data; + }, + invalidateQueryIds: ['get_tickets', 'get_ticket'], + onSuccess, + onError, + }); +}; + +export const useCloneTicket = ({ + onSuccess, + onError, +}: UseTicketMutationCallbacks = {}) => { + const gmp = useGmp(); + return useCloneMutation({ + gmpMethod: async data => { + const response = await gmp.ticket.clone(data); + return response.data; + }, + entityType: 'ticket', + invalidateQueryIds: ['get_tickets'], + onSuccess, + onError, + }); +}; + +export const useDeleteTicket = ({ + onSuccess, + onError, +}: UseTicketMutationCallbacks = {}) => { + const gmp = useGmp(); + return useDeleteMutation({ + gmpMethod: ({id}) => gmp.ticket.delete({id}), + entityType: 'ticket', + invalidateQueryIds: ['get_tickets', 'get_ticket'], + onSuccess, + onError, + }); +}; + +export const useDownloadTicket = ({ + onSuccess, + onError, +}: UseTicketMutationCallbacks> = {}) => { + const gmp = useGmp(); + return useGmpMutation<{id: string}, Response>({ + gmpMethod: ({id}: {id: string}) => gmp.ticket.export({id}), + onSuccess, + onError, + }); +}; diff --git a/src/web/hooks/use-query/tickets.ts b/src/web/hooks/use-query/tickets.ts index d0a45fc3dc..e2aa36deba 100644 --- a/src/web/hooks/use-query/tickets.ts +++ b/src/web/hooks/use-query/tickets.ts @@ -3,46 +3,15 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {type EntityActionData} from 'gmp/commands/entity'; -import type Response from 'gmp/http/response'; import {type FilterType} from 'gmp/models/filter'; -import { - type default as Ticket, - type TicketStatusValue, -} from 'gmp/models/ticket'; +import type Ticket from 'gmp/models/ticket'; import useGmp from 'web/hooks/useGmp'; import useGetEntities from 'web/queries/useGetEntities'; -import useGetEntity from 'web/queries/useGetEntity'; -import useGmpMutation from 'web/queries/useGmpMutation'; interface UseGetTicketsParams { filter?: FilterType; } -interface UseGetTicketParams { - id: string; -} - -interface UseTicketMutationCallbacks { - onSuccess?: (response: TResponse) => void; - onError?: (error: Error) => void; -} - -interface TicketCreateInput { - resultId: string; - userId: string; - note?: string; -} - -interface TicketSaveInput { - id: string; - openNote?: string; - fixedNote?: string; - closedNote?: string; - status: TicketStatusValue; - userId: string; -} - export const useGetTickets = ({filter}: UseGetTicketsParams = {}) => { const gmp = useGmp(); return useGetEntities({ @@ -52,85 +21,3 @@ export const useGetTickets = ({filter}: UseGetTicketsParams = {}) => { keepPreviousData: true, }); }; - -export const useGetTicket = ({id}: UseGetTicketParams) => { - const gmp = useGmp(); - return useGetEntity({ - gmpMethod: gmp.ticket.get.bind(gmp.ticket), - queryId: 'get_ticket', - id, - }); -}; - -export const useCreateTicket = ({ - onSuccess, - onError, -}: UseTicketMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation({ - gmpMethod: async data => { - const response = await gmp.ticket.create(data); - return response.data; - }, - invalidateQueryIds: ['get_tickets'], - onSuccess, - onError, - }); -}; - -export const useSaveTicket = ({ - onSuccess, - onError, -}: UseTicketMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation({ - gmpMethod: async data => { - const response = await gmp.ticket.save(data); - return response.data; - }, - invalidateQueryIds: ['get_tickets', 'get_ticket'], - onSuccess, - onError, - }); -}; - -export const useCloneTicket = ({ - onSuccess, - onError, -}: UseTicketMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation<{id: string}, EntityActionData>({ - gmpMethod: async ({id}: {id: string}) => { - const response = await gmp.ticket.clone({id}); - return response.data; - }, - invalidateQueryIds: ['get_tickets'], - onSuccess, - onError, - }); -}; - -export const useDeleteTicket = ({ - onSuccess, - onError, -}: UseTicketMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation<{id: string}, void>({ - gmpMethod: ({id}: {id: string}) => gmp.ticket.delete({id}), - invalidateQueryIds: ['get_tickets', 'get_ticket'], - onSuccess, - onError, - }); -}; - -export const useDownloadTicket = ({ - onSuccess, - onError, -}: UseTicketMutationCallbacks> = {}) => { - const gmp = useGmp(); - return useGmpMutation<{id: string}, Response>({ - gmpMethod: ({id}: {id: string}) => gmp.ticket.export({id}), - onSuccess, - onError, - }); -}; diff --git a/src/web/hooks/use-query/user.ts b/src/web/hooks/use-query/user.ts new file mode 100644 index 0000000000..39b55d2e2b --- /dev/null +++ b/src/web/hooks/use-query/user.ts @@ -0,0 +1,135 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type EntityActionData} from 'gmp/commands/entity'; +import {type UserCommandDeleteParams} from 'gmp/commands/user'; +import type Response from 'gmp/http/response'; +import type User from 'gmp/models/user'; +import useGmp from 'web/hooks/useGmp'; +import {type RefetchIntervalFn} from 'web/queries/helpers'; +import useCloneMutation from 'web/queries/useCloneMutation'; +import useCreateMutation from 'web/queries/useCreateMutation'; +import useDeleteMutation from 'web/queries/useDeleteMutation'; +import useGetEntity from 'web/queries/useGetEntity'; +import useGmpMutation from 'web/queries/useGmpMutation'; + +interface UseGetUserParams { + id: string; + refetchInterval?: RefetchIntervalFn; +} + +interface UseUserMutationCallbacks { + onSuccess?: (response: TResponse) => void; + onError?: (error: Error) => void; +} + +interface UserCreateInput { + accessHosts: string[]; + authMethod: string; + comment: string; + groupIds: string[]; + hostsAllow: string; + name: string; + password: string; + roleIds: string[]; +} + +interface UserSaveInput extends UserCreateInput { + id: string; + oldName?: string; +} + +interface UserDeleteInput extends UserCommandDeleteParams { + name: string | undefined; +} + +export const useGetUser = ({id, refetchInterval}: UseGetUserParams) => { + const gmp = useGmp(); + return useGetEntity({ + gmpMethod: gmp.user.get.bind(gmp.user), + queryId: 'get_user', + id, + refetchInterval, + }); +}; + +export const useCreateUser = ({ + onSuccess, + onError, +}: UseUserMutationCallbacks = {}) => { + const gmp = useGmp(); + return useCreateMutation({ + gmpMethod: async data => { + const response = await gmp.user.create(data); + return response.data; + }, + entityType: 'user', + invalidateQueryIds: ['get_users'], + onSuccess, + onError, + }); +}; + +export const useSaveUser = ({ + onSuccess, + onError, +}: UseUserMutationCallbacks = {}) => { + const gmp = useGmp(); + return useGmpMutation({ + gmpMethod: async data => { + await gmp.user.save({ + ...data, + oldName: data.oldName ?? data.name, + }); + }, + invalidateQueryIds: ['get_users', 'get_user'], + onSuccess, + onError, + }); +}; + +export const useCloneUser = ({ + onSuccess, + onError, +}: UseUserMutationCallbacks = {}) => { + const gmp = useGmp(); + return useCloneMutation({ + gmpMethod: async ({id}) => { + const response = await gmp.user.clone({id}); + return response.data; + }, + entityType: 'user', + invalidateQueryIds: ['get_users'], + onSuccess, + onError, + }); +}; + +export const useDeleteUser = ({ + onSuccess, + onError, +}: UseUserMutationCallbacks = {}) => { + const gmp = useGmp(); + return useDeleteMutation({ + gmpMethod: ({id, inheritorId}: UserDeleteInput) => + gmp.user.delete({id, inheritorId}), + entityType: 'user', + invalidateQueryIds: ['get_users', 'get_user'], + onSuccess, + onError, + }); +}; + +export const useDownloadUser = ({ + onSuccess, + onError, +}: UseUserMutationCallbacks> = {}) => { + const gmp = useGmp(); + return useGmpMutation<{id: string}, Response>({ + gmpMethod: (entity: {id: string}) => gmp.user.export(entity), + onSuccess, + onError, + }); +}; diff --git a/src/web/hooks/use-query/users.ts b/src/web/hooks/use-query/users.ts index a5de2994f7..4c608935bf 100644 --- a/src/web/hooks/use-query/users.ts +++ b/src/web/hooks/use-query/users.ts @@ -3,15 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {type EntityActionData} from 'gmp/commands/entity'; import type Response from 'gmp/http/response'; import {type FilterType} from 'gmp/models/filter'; import {isFilterType} from 'gmp/models/filter/utils'; import type User from 'gmp/models/user'; import useGmp from 'web/hooks/useGmp'; -import {type RefetchIntervalFn} from 'web/queries/helpers'; import useGetEntities from 'web/queries/useGetEntities'; -import useGetEntity from 'web/queries/useGetEntity'; import useGmpMutation from 'web/queries/useGmpMutation'; interface UseGetUsersParams { @@ -19,32 +16,11 @@ interface UseGetUsersParams { enabled?: boolean; } -interface UseGetUserParams { - id: string; - refetchInterval?: RefetchIntervalFn; -} - interface UseUserMutationCallbacks { onSuccess?: (response: TResponse) => void; onError?: (error: Error) => void; } -interface UserCreateInput { - accessHosts: string[]; - authMethod: string; - comment: string; - groupIds: string[]; - hostsAllow: string; - name: string; - password: string; - roleIds: string[]; -} - -interface UserSaveInput extends UserCreateInput { - id: string; - oldName?: string; -} - interface BulkDeleteUsersInput { users: User[]; options?: {inheritor_id?: string}; @@ -66,81 +42,6 @@ export const useGetUsers = ({ }); }; -export const useGetUser = ({id, refetchInterval}: UseGetUserParams) => { - const gmp = useGmp(); - return useGetEntity({ - gmpMethod: gmp.user.get.bind(gmp.user), - queryId: 'get_user', - id, - refetchInterval, - }); -}; - -export const useCreateUser = ({ - onSuccess, - onError, -}: UseUserMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation({ - gmpMethod: async data => { - const response = await gmp.user.create(data); - return response.data; - }, - invalidateQueryIds: ['get_users'], - onSuccess, - onError, - }); -}; - -export const useSaveUser = ({ - onSuccess, - onError, -}: UseUserMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation({ - gmpMethod: async data => { - const response = await gmp.user.save({ - ...data, - oldName: data.oldName ?? data.name, - }); - return response.data; - }, - invalidateQueryIds: ['get_users', 'get_user'], - onSuccess, - onError, - }); -}; - -export const useCloneUser = ({ - onSuccess, - onError, -}: UseUserMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation<{id: string}, EntityActionData>({ - gmpMethod: async ({id}: {id: string}) => { - const response = await gmp.user.clone({id}); - return response.data; - }, - invalidateQueryIds: ['get_users'], - onSuccess, - onError, - }); -}; - -export const useDeleteUser = ({ - onSuccess, - onError, -}: UseUserMutationCallbacks = {}) => { - const gmp = useGmp(); - return useGmpMutation<{id: string; inheritorId?: string}, void>({ - gmpMethod: ({id, inheritorId}: {id: string; inheritorId?: string}) => - gmp.user.delete({id, inheritorId: inheritorId ?? ''}), - invalidateQueryIds: ['get_users', 'get_user'], - onSuccess, - onError, - }); -}; - export const useBulkDeleteUsers = ({ onSuccess, onError, @@ -154,18 +55,6 @@ export const useBulkDeleteUsers = ({ }); }; -export const useDownloadUser = ({ - onSuccess, - onError, -}: UseUserMutationCallbacks> = {}) => { - const gmp = useGmp(); - return useGmpMutation<{id: string}, Response>({ - gmpMethod: (entity: {id: string}) => gmp.user.export(entity), - onSuccess, - onError, - }); -}; - export const useBulkExportUsers = ({ onSuccess, onError, diff --git a/src/web/hooks/use-query/web-application-target.ts b/src/web/hooks/use-query/web-application-target.ts new file mode 100644 index 0000000000..ae78e215e0 --- /dev/null +++ b/src/web/hooks/use-query/web-application-target.ts @@ -0,0 +1,93 @@ +/* SPDX-FileCopyrightText: 2026 Greenbone AG + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import {type EntityActionData} from 'gmp/commands/entity'; +import { + type WebApplicationTargetCreateParams, + type WebApplicationTargetSaveParams, +} from 'gmp/commands/web-application-target'; +import useGmp from 'web/hooks/useGmp'; +import useCloneMutation from 'web/queries/useCloneMutation'; +import useCreateMutation from 'web/queries/useCreateMutation'; +import useMoveToTrashCan from 'web/queries/useMoveToTrashCan'; +import useSaveMutation from 'web/queries/useSaveMutation'; + +interface UseCreateWebApplicationTargetParams { + onSuccess?: (data: EntityActionData) => void; + onError?: (error: Error) => void; +} + +interface UseModifyWebApplicationTargetParams { + onSuccess?: () => void; + onError?: (error: Error) => void; +} + +export const useCreateWebApplicationTarget = ({ + onSuccess, + onError, +}: UseCreateWebApplicationTargetParams) => { + const gmp = useGmp(); + + return useCreateMutation({ + gmpMethod: async data => { + const response = await gmp.webapplicationtarget.create(data); + return response.data; + }, + entityType: 'webapplicationtarget', + invalidateQueryIds: ['get_web_application_targets'], + onError, + onSuccess, + }); +}; + +export const useSaveWebApplicationTarget = ({ + onError, + onSuccess, +}: UseModifyWebApplicationTargetParams) => { + const gmp = useGmp(); + + return useSaveMutation({ + gmpMethod: async data => { + await gmp.webapplicationtarget.save(data); + }, + entityType: 'webapplicationtarget', + invalidateQueryIds: ['get_web_application_targets'], + onError, + onSuccess, + }); +}; + +export const useDeleteWebApplicationTarget = ({ + onError, + onSuccess, +}: UseModifyWebApplicationTargetParams) => { + const gmp = useGmp(); + + return useMoveToTrashCan({ + gmpMethod: ({id}) => gmp.webapplicationtarget.delete({id}), + entityType: 'webapplicationtarget', + invalidateQueryIds: ['get_web_application_targets'], + onSuccess, + onError, + }); +}; + +export const useCloneWebApplicationTarget = ({ + onError, + onSuccess, +}: UseCreateWebApplicationTargetParams) => { + const gmp = useGmp(); + + return useCloneMutation({ + gmpMethod: async ({id}) => { + const response = await gmp.webapplicationtarget.clone({id}); + return response.data; + }, + entityType: 'webapplicationtarget', + invalidateQueryIds: ['get_web_application_targets'], + onError, + onSuccess, + }); +}; diff --git a/src/web/hooks/use-query/web-application-targets.ts b/src/web/hooks/use-query/web-application-targets.ts index ff1a174938..09d040efc0 100644 --- a/src/web/hooks/use-query/web-application-targets.ts +++ b/src/web/hooks/use-query/web-application-targets.ts @@ -3,11 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import {type EntityActionResponse} from 'gmp/commands/entity'; -import { - type WebApplicationTargetCreateParams, - type WebApplicationTargetSaveParams, -} from 'gmp/commands/web-application-target'; import type Rejection from 'gmp/http/rejection'; import type Response from 'gmp/http/response'; import {type XmlMeta} from 'gmp/http/transform/fast-xml'; @@ -16,20 +11,11 @@ import {isFilterType} from 'gmp/models/filter/utils'; import type WebApplicationTarget from 'gmp/models/web-application-target'; import useGmp from 'web/hooks/useGmp'; import useTranslation from 'web/hooks/useTranslation'; -import useCloneMutation from 'web/queries/useCloneMutation'; -import useCreateMutation from 'web/queries/useCreateMutation'; import useGetEntities from 'web/queries/useGetEntities'; import useGmpMutation from 'web/queries/useGmpMutation'; -import useMoveToTrashCan from 'web/queries/useMoveToTrashCan'; -import useSaveMutation from 'web/queries/useSaveMutation'; type WebApplicationTargetBulkInput = WebApplicationTarget[] | FilterType; -interface UseCreateWebApplicationTargetParams { - onSuccess?: (data: EntityActionResponse) => void; - onError?: (error: Error) => void; -} - interface UseModifyWebApplicationTargetParams { onSuccess?: () => void; onError?: (error: Error) => void; @@ -49,74 +35,6 @@ export const useGetWebApplicationTargets = ({ }); }; -export const useCreateWebApplicationTarget = ({ - onSuccess, - onError, -}: UseCreateWebApplicationTargetParams) => { - const gmp = useGmp(); - - return useCreateMutation< - WebApplicationTargetCreateParams, - EntityActionResponse, - Rejection - >({ - gmpMethod: gmp.webapplicationtarget.create.bind(gmp.webapplicationtarget), - entityType: 'webapplicationtarget', - invalidateQueryIds: ['get_web_application_targets'], - onError, - onSuccess, - }); -}; - -export const useSaveWebApplicationTarget = ({ - onError, - onSuccess, -}: UseModifyWebApplicationTargetParams) => { - const gmp = useGmp(); - - return useSaveMutation< - WebApplicationTargetSaveParams, - EntityActionResponse, - Rejection - >({ - gmpMethod: gmp.webapplicationtarget.save.bind(gmp.webapplicationtarget), - entityType: 'webapplicationtarget', - invalidateQueryIds: ['get_web_application_targets'], - onError, - onSuccess, - }); -}; - -export const useDeleteWebApplicationTarget = ({ - onError, - onSuccess, -}: UseModifyWebApplicationTargetParams) => { - const gmp = useGmp(); - - return useMoveToTrashCan({ - gmpMethod: gmp.webapplicationtarget.delete.bind(gmp.webapplicationtarget), - entityType: 'webapplicationtarget', - invalidateQueryIds: ['get_web_application_targets'], - onSuccess, - onError, - }); -}; - -export const useCloneWebApplicationTarget = ({ - onError, - onSuccess, -}: UseCreateWebApplicationTargetParams) => { - const gmp = useGmp(); - - return useCloneMutation({ - gmpMethod: gmp.webapplicationtarget.clone.bind(gmp.webapplicationtarget), - entityType: 'webapplicationtarget', - invalidateQueryIds: ['get_web_application_targets'], - onError, - onSuccess, - }); -}; - export const useBulkDeleteWebApplicationTargets = ({ onError, onSuccess, diff --git a/src/web/pages/agent-groups/AgentGroupsComponent.tsx b/src/web/pages/agent-groups/AgentGroupsComponent.tsx index 5506604cea..ebf60fca9f 100644 --- a/src/web/pages/agent-groups/AgentGroupsComponent.tsx +++ b/src/web/pages/agent-groups/AgentGroupsComponent.tsx @@ -5,8 +5,6 @@ import React, {useState} from 'react'; import {type EntityActionData} from 'gmp/commands/entity'; -import type Response from 'gmp/http/response'; -import {type XmlMeta} from 'gmp/http/transform/fast-xml'; import type AgentGroup from 'gmp/models/agent-group'; import {isDefined} from 'gmp/utils/identity'; import { @@ -14,7 +12,7 @@ import { useCreateAgentGroup, useDeleteAgentGroup, useSaveAgentGroup, -} from 'web/hooks/use-query/agent-groups'; +} from 'web/hooks/use-query/agent-group'; import useTranslation from 'web/hooks/useTranslation'; import AgentGroupsDialog, { type AgentGroupDialogData, @@ -22,7 +20,7 @@ import AgentGroupsDialog, { interface AgentGroupsComponentRenderProps { create: () => void; - clone: (entity: AgentGroup) => Promise>; + clone: (entity: AgentGroup) => Promise; delete: (entity: AgentGroup) => Promise; edit: (entity: AgentGroup) => void; } @@ -31,7 +29,7 @@ interface AgentGroupsComponentProps { children: (actions: AgentGroupsComponentRenderProps) => React.ReactNode; onCloned?: () => void; onCloneError?: (error: Error) => void; - onCreated?: (response: Response) => void; + onCreated?: (data: EntityActionData) => void; onCreateError?: (error: Error) => void; onDeleted?: () => void; onDeleteError?: (error: Error) => void; diff --git a/src/web/pages/agent-groups/__tests__/AgentGroupsComponent.test.tsx b/src/web/pages/agent-groups/__tests__/AgentGroupsComponent.test.tsx index f608f8ae6b..ea9b34f653 100644 --- a/src/web/pages/agent-groups/__tests__/AgentGroupsComponent.test.tsx +++ b/src/web/pages/agent-groups/__tests__/AgentGroupsComponent.test.tsx @@ -7,7 +7,6 @@ import {describe, expect, test, testing} from '@gsa/testing'; import {fireEvent, rendererWith, screen, wait} from 'web/testing'; import {type EntityActionData} from 'gmp/commands/entity'; import Response from 'gmp/http/response'; -import {type XmlMeta} from 'gmp/http/transform/fast-xml'; import AgentGroup from 'gmp/models/agent-group'; import {createSession} from 'gmp/testing'; import Button from 'web/components/form/Button'; @@ -19,8 +18,10 @@ const sampleAgentGroup: AgentGroup = new AgentGroup({ }); const createGmp = () => { - const createMock = testing.fn().mockResolvedValue({id: '123'}); - const cloneMock = testing.fn().mockResolvedValue({id: 'cloned'}); + const createMock = testing.fn().mockResolvedValue(new Response({id: '123'})); + const cloneMock = testing + .fn() + .mockResolvedValue(new Response({id: 'cloned'})); const deleteMock = testing.fn().mockResolvedValue(undefined); const saveMock = testing.fn().mockResolvedValue(undefined); @@ -52,9 +53,7 @@ describe('AgentGroupsComponent tests', () => { type AgentGroupsActions = { create: () => void; - clone: ( - entity: AgentGroup, - ) => Promise>; + clone: (entity: AgentGroup) => Promise; delete: (entity: AgentGroup) => Promise; edit: (entity: AgentGroup) => void; }; @@ -83,17 +82,14 @@ describe('AgentGroupsComponent tests', () => { await wait(); - expect(saveMock).toHaveBeenCalledWith( - expect.objectContaining({ - id: 'g1', - name: 'group1', - comment: '', - scannerId: '', - agentIds: [], - schedulerCronTime: '0 */12 * * *', - }), - expect.anything(), - ); + expect(saveMock).toHaveBeenCalledWith({ + id: 'g1', + name: 'group1', + comment: '', + scannerId: '', + agentIds: [], + schedulerCronTime: '0 */12 * * *', + }); expect(onSaved).toHaveBeenCalled(); }); @@ -163,7 +159,7 @@ describe('AgentGroupsComponent tests', () => { cloneMock.mockRejectedValue(error); let cloneFn: - | ((entity: AgentGroup) => Promise>) + | ((entity: AgentGroup) => Promise) | undefined; const {render} = rendererWith({gmp}); diff --git a/src/web/pages/agents/AgentComponent.tsx b/src/web/pages/agents/AgentComponent.tsx index d5a7fab2dd..55b780df42 100644 --- a/src/web/pages/agents/AgentComponent.tsx +++ b/src/web/pages/agents/AgentComponent.tsx @@ -4,9 +4,8 @@ */ import React, {useState} from 'react'; -import type Rejection from 'gmp/http/rejection'; import type Agent from 'gmp/models/agent'; -import {useDeleteAgent, useModifyAgent} from 'web/hooks/use-query/agents'; +import {useDeleteAgent, useModifyAgent} from 'web/hooks/use-query/agent'; import useTranslation from 'web/hooks/useTranslation'; import AgentDialog, {type AgentDialogState} from 'web/pages/agents/AgentDialog'; @@ -19,9 +18,9 @@ interface AgentComponentRenderProps { interface AgentComponentProps { children: (actions: AgentComponentRenderProps) => React.ReactNode; onDeleted?: () => void; - onDeleteError?: (error: Rejection) => void; + onDeleteError?: (error: Error) => void; onSaved?: () => void; - onSaveError?: (error: Rejection) => void; + onSaveError?: (error: Error) => void; } const AgentComponent = ({ diff --git a/src/web/pages/agents/AgentListPage.tsx b/src/web/pages/agents/AgentListPage.tsx index 1b08d19aec..a2f0abc892 100644 --- a/src/web/pages/agents/AgentListPage.tsx +++ b/src/web/pages/agents/AgentListPage.tsx @@ -21,10 +21,10 @@ import DialogNotification from 'web/components/notification/DialogNotification'; import useDialogNotification from 'web/components/notification/useDialogNotification'; import BulkTags from 'web/entities/BulkTags'; import EntitiesPage from 'web/entities/EntitiesPage'; +import {useDownloadAgentSupportBundle} from 'web/hooks/use-query/agent'; import { useBulkAuthorizeAgents, useBulkDeleteAgents, - useDownloadAgentSupportBundle, useBulkDisableUpdateToLatestAgents, useBulkEnableUpdateToLatestAgents, useBulkRevokeAgents, diff --git a/src/web/pages/agents/AgentListPageToolBarIcons.tsx b/src/web/pages/agents/AgentListPageToolBarIcons.tsx index 2a0f033bae..c0e8d032c9 100644 --- a/src/web/pages/agents/AgentListPageToolBarIcons.tsx +++ b/src/web/pages/agents/AgentListPageToolBarIcons.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import type Rejection from 'gmp/http/rejection'; import type Agent from 'gmp/models/agent'; import {isDefined} from 'gmp/utils/identity'; import DateTime from 'web/components/date/DateTime'; @@ -15,7 +14,7 @@ import useTranslation from 'web/hooks/useTranslation'; interface AgentsListPageToolBarIconsProps { agents?: Agent[]; - onError: (error: Rejection) => void; + onError: (error: Error) => void; } const AgentsListPageToolBarIcons = ({ diff --git a/src/web/pages/audits/DetailsPage.tsx b/src/web/pages/audits/DetailsPage.tsx index 24d1a61f0a..c0a369828e 100644 --- a/src/web/pages/audits/DetailsPage.tsx +++ b/src/web/pages/audits/DetailsPage.tsx @@ -36,7 +36,7 @@ import EntityPermissions, { } from 'web/entity/EntityPermissions'; import {goToDetails, goToList} from 'web/entity/navigation'; import EntityTags from 'web/entity/Tags'; -import {useGetAudit} from 'web/hooks/use-query/audits'; +import {useGetAudit} from 'web/hooks/use-query/audit'; import {useGetPermissions} from 'web/hooks/use-query/permissions'; import useGmp from 'web/hooks/useGmp'; import useTranslation from 'web/hooks/useTranslation'; diff --git a/src/web/pages/container-image-targets/ContainerImageTargetsComponent.tsx b/src/web/pages/container-image-targets/ContainerImageTargetsComponent.tsx index ae8be1986e..bd348b968b 100644 --- a/src/web/pages/container-image-targets/ContainerImageTargetsComponent.tsx +++ b/src/web/pages/container-image-targets/ContainerImageTargetsComponent.tsx @@ -4,7 +4,7 @@ */ import React, {useState} from 'react'; -import {type EntityActionResponse} from 'gmp/commands/entity'; +import {type EntityActionData} from 'gmp/commands/entity'; import { type OciImageTargetCreateParams, type OciImageTargetSaveParams, @@ -22,7 +22,7 @@ import { useCreateOciImageTarget, useDeleteOciImageTarget, useSaveOciImageTarget, -} from 'web/hooks/use-query/oci-image-targets'; +} from 'web/hooks/use-query/oci-image-target'; import useGmp from 'web/hooks/useGmp'; import useTranslation from 'web/hooks/useTranslation'; import ContainerImageTargetsDialog, { @@ -36,7 +36,7 @@ interface ContainerImageTargetComponentRenderProps { delete: (entity: OciImageTarget) => Promise; edit: (entity: OciImageTarget) => Promise; create: () => Promise; - clone: (entity: OciImageTarget) => Promise; + clone: (entity: OciImageTarget) => Promise; download: (entity: OciImageTarget) => Promise; save: (entity: OciImageTarget) => Promise; } @@ -49,7 +49,7 @@ interface ContainerImageTargetsComponentProps { onDeleteError?: (error: Error) => void; onSaved?: () => void; onSaveError?: (error: Error) => void; - onCreated?: (response: EntityActionResponse) => void; + onCreated?: (response: EntityActionData) => void; onCreateError?: (error: Error) => void; onCloned?: () => void; onCloneError?: (error: Error) => void; diff --git a/src/web/pages/container-image-targets/__tests__/ContainerImageTargetComponent.test.tsx b/src/web/pages/container-image-targets/__tests__/ContainerImageTargetComponent.test.tsx index c87b9b92a1..7c007b20f1 100644 --- a/src/web/pages/container-image-targets/__tests__/ContainerImageTargetComponent.test.tsx +++ b/src/web/pages/container-image-targets/__tests__/ContainerImageTargetComponent.test.tsx @@ -16,12 +16,12 @@ type MaybeMock = T | ((...args: unknown[]) => Promise); type CreateGmpParams = { getAllCredentials?: unknown[]; - createOciImageTarget?: MaybeMock<{id: string}>; - saveOciImageTarget?: MaybeMock<{id: string}>; - cloneOciImageTarget?: MaybeMock<{id: string}>; + createOciImageTarget?: MaybeMock>; + saveOciImageTarget?: MaybeMock>; + cloneOciImageTarget?: MaybeMock>; deleteOciImageTarget?: MaybeMock; exportOciImageTarget?: MaybeMock; - createCredentialResponse?: MaybeMock; + createCredentialResponse?: MaybeMock>; createCredential?: MaybeMock; }; @@ -43,12 +43,12 @@ const assertHandlerError = (mock: MockFn, error: Error) => { const createGmp = ({ getAllCredentials = [], - createOciImageTarget = {id: 'created-id'}, - saveOciImageTarget = {id: 'saved-id'}, - cloneOciImageTarget = {id: 'cloned-id'}, + createOciImageTarget = new Response({id: 'created-id'}), + saveOciImageTarget = new Response({id: 'saved-id'}), + cloneOciImageTarget = new Response({id: 'cloned-id'}), deleteOciImageTarget, exportOciImageTarget = new Response('some-data'), - createCredentialResponse = {data: {id: 'cred-id'}}, + createCredentialResponse = new Response({id: 'cred-id'}), createCredential = testing.fn().mockResolvedValue(createCredentialResponse), }: CreateGmpParams = {}): { credentials: {getAll: ReturnType}; @@ -218,7 +218,7 @@ describe('ContainerImageTargetsComponent tests', () => { screen.queryByText('Edit Container Image Target - foo'), ).not.toBeInTheDocument(); expect(onSaveError).not.toHaveBeenCalled(); - expect(onSaved).toHaveBeenCalledWith({id: 'saved-id'}); + expect(onSaved).toHaveBeenCalledWith(undefined); }); test('should call onSaveError if saving fails and show error', async () => { @@ -506,7 +506,7 @@ describe('ContainerImageTargetsComponent tests', () => { expect( screen.queryByText('Edit Container Image Target - foo'), ).not.toBeInTheDocument(); - expect(onSaved).toHaveBeenCalledWith({id: 'saved-id'}); + expect(onSaved).toHaveBeenCalledWith(undefined); }); test('should handle editing target with undefined excludeImages', async () => { @@ -539,7 +539,7 @@ describe('ContainerImageTargetsComponent tests', () => { expect( screen.queryByText('Edit Container Image Target - foo'), ).not.toBeInTheDocument(); - expect(onSaved).toHaveBeenCalledWith({id: 'saved-id'}); + expect(onSaved).toHaveBeenCalledWith(undefined); }); test('should render edit dialog when both imageReferences and excludeImages are undefined', async () => { diff --git a/src/web/pages/credential-store/CredentialStorePage.tsx b/src/web/pages/credential-store/CredentialStorePage.tsx index b9c42d2f02..a21bf17ec5 100644 --- a/src/web/pages/credential-store/CredentialStorePage.tsx +++ b/src/web/pages/credential-store/CredentialStorePage.tsx @@ -22,10 +22,10 @@ import TableCol from 'web/components/table/TableCol'; import TableData from 'web/components/table/TableData'; import TableRow from 'web/components/table/TableRow'; import { - useGetCredentialStores, useEditCredentialStore, useVerifyCredentialStore, } from 'web/hooks/use-query/credential-store'; +import {useGetCredentialStores} from 'web/hooks/use-query/credential-stores'; import useTranslation from 'web/hooks/useTranslation'; import ConnectionStatusPill from 'web/pages/credential-store/ConnectionStatusPill'; import CredentialStoreDialog, { diff --git a/src/web/pages/policies/DetailsPage.tsx b/src/web/pages/policies/DetailsPage.tsx index 685e169bca..94e9e2c275 100644 --- a/src/web/pages/policies/DetailsPage.tsx +++ b/src/web/pages/policies/DetailsPage.tsx @@ -27,7 +27,7 @@ import EntityPermissions from 'web/entity/EntityPermissions'; import {goToDetails, goToList} from 'web/entity/navigation'; import EntityTags from 'web/entity/Tags'; import {useGetPermissions} from 'web/hooks/use-query/permissions'; -import {useGetPolicy} from 'web/hooks/use-query/policies'; +import {useGetPolicy} from 'web/hooks/use-query/policy'; import useTranslation from 'web/hooks/useTranslation'; import PolicyDetails from 'web/pages/policies/Details'; import PolicyComponent from 'web/pages/policies/PoliciesComponent'; diff --git a/src/web/pages/policies/PoliciesComponent.tsx b/src/web/pages/policies/PoliciesComponent.tsx index b3d028d084..98ff2af39d 100644 --- a/src/web/pages/policies/PoliciesComponent.tsx +++ b/src/web/pages/policies/PoliciesComponent.tsx @@ -5,6 +5,7 @@ import React, {useState, useCallback, useEffect} from 'react'; import {useDispatch} from 'react-redux'; +import {type EntityActionData} from 'gmp/commands/entity'; import {type NvtFamily} from 'gmp/commands/nvt-families'; import { type ScanConfigFamilyNvt, @@ -285,7 +286,7 @@ const PolicyComponent = ({ setScheduleId(data.id); }; - const handleTargetCreated = ({data}) => { + const handleTargetCreated = (data: EntityActionData) => { fetchTargets(); setTargetId(data.id); }; diff --git a/src/web/pages/portlists/PortListComponent.tsx b/src/web/pages/portlists/PortListComponent.tsx index d6b32ec828..7af87bfb27 100644 --- a/src/web/pages/portlists/PortListComponent.tsx +++ b/src/web/pages/portlists/PortListComponent.tsx @@ -5,7 +5,7 @@ import React, {useState} from 'react'; import { - type EntityActionResponse, + type EntityActionData, type EntityCommandParams, } from 'gmp/commands/entity'; import { @@ -19,23 +19,22 @@ import { } from 'gmp/models/port-list'; import {isDefined} from 'gmp/utils/identity'; import {shorten} from 'gmp/utils/string'; -import useEntityClone, { - type EntityCloneResponse, -} from 'web/entity/hooks/useEntityClone'; -import useEntityCreate, { - type EntityCreateResponse, -} from 'web/entity/hooks/useEntityCreate'; -import useEntityDelete from 'web/entity/hooks/useEntityDelete'; import useEntityDownload, { type OnDownloadedFunc, } from 'web/entity/hooks/useEntityDownload'; -import useEntitySave, { - type EntitySaveResponse, -} from 'web/entity/hooks/useEntitySave'; +import { + useClonePortList, + useCreatePortList, + useCreatePortRange, + useDeletePortList, + useDeletePortRange, + useImportPortList, + useSavePortList, +} from 'web/hooks/use-query/port-list'; import useGmp from 'web/hooks/useGmp'; import useTranslation from 'web/hooks/useTranslation'; import PortListsDialog, { - type SavePortListData, + type PortListDialogSaveData, } from 'web/pages/portlists/PortListDialog'; import ImportPortListDialog, { type PortListImportDialogState, @@ -65,17 +64,17 @@ interface PortListComponentRenderProps { interface PortListComponentProps { children: (props: PortListComponentRenderProps) => React.ReactNode; onCloneError?: (error: Error) => void; - onCloned?: (response: EntityCloneResponse) => void; + onCloned?: (response: EntityActionData) => void; onCreateError?: (error: Error) => void; - onCreated?: (response: EntityCreateResponse) => void; + onCreated?: (response: EntityActionData) => void; onDeleteError?: (error: Error) => void; onDeleted?: () => void; onDownloadError?: (error: Error) => void; onDownloaded?: OnDownloadedFunc; onImportError?: (error: Error) => void; - onImported?: (response: EntityActionResponse) => void; + onImported?: (response: EntityActionData) => void; onSaveError?: (error: Error) => void; - onSaved?: (response: EntitySaveResponse) => void; + onSaved?: (response: EntityActionData) => void; } const PortListComponent = ({ @@ -106,27 +105,43 @@ const PortListComponent = ({ const [createdPortRanges, setCreatedPortRanges] = useState([]); const [deletedPortRanges, setDeletedPortRanges] = useState([]); - const handleSave = useEntitySave( - data => gmp.portlist.save(data), - { - onSaveError, - onSaved, - }, - ); - const handleCreate = useEntityCreate( - data => gmp.portlist.create(data), - { - onCreated, - onCreateError, - }, - ); - const handleClone = useEntityClone( - entity => gmp.portlist.clone(entity), - { - onCloned, - onCloneError, - }, - ); + const savePortListMutation = useSavePortList({ + onSuccess: onSaved, + onError: onSaveError, + }); + const savePortList = async (data: PortListCommandSaveParams) => + await savePortListMutation.mutateAsync(data); + + const createPortListMutation = useCreatePortList({ + onSuccess: onCreated, + onError: onCreateError, + }); + const createPortList = async (data: PortListCommandCreateParams) => + await createPortListMutation.mutateAsync(data); + + const clonePortListMutation = useClonePortList({ + onSuccess: onCloned, + onError: onCloneError, + }); + const clonePortList = async (data: PortList) => + await clonePortListMutation.mutateAsync({id: data.id, name: data.name}); + + const deletePortListMutation = useDeletePortList({ + onSuccess: onDeleted, + onError: onDeleteError, + }); + const deletePortList = async (data: PortList) => + await deletePortListMutation.mutateAsync({id: data.id, name: data.name}); + + const importPortListMutation = useImportPortList({ + onSuccess: onImported, + onError: onImportError, + }); + + const createPortRangeMutation = useCreatePortRange(); + + const deletePortRangeMutation = useDeletePortRange(); + const handleDownload = useEntityDownload( entity => gmp.portlist.export(entity), { @@ -134,13 +149,6 @@ const PortListComponent = ({ onDownloaded, }, ); - const handleDelete = useEntityDelete( - entity => gmp.portlist.delete(entity), - { - onDeleteError, - onDeleted, - }, - ); const openPortListDialog = async (entity?: PortList) => { if (entity) { @@ -199,9 +207,8 @@ const PortListComponent = ({ }; const handleDeletePortRange = async (range: PortRange) => { - await gmp.portlist.deletePortRange({ + await deletePortRangeMutation.mutateAsync({ id: range.id as string, - portListId: range.portListId, }); }; @@ -211,25 +218,18 @@ const PortListComponent = ({ portRangeEnd: number; portType: ProtocolType; }) => { - const response = await gmp.portlist.createPortRange(data); - return response.data.id; + const response = await createPortRangeMutation.mutateAsync(data); + return response.id; }; const handleImportPortList = async (data: PortListImportDialogState) => { - try { - const response = await gmp.portlist.import(data); - if (isDefined(onImported)) { - onImported(response); - } - closeImportDialog(); - } catch (error) { - if (isDefined(onImportError)) { - onImportError(error as Rejection); - } - } + await importPortListMutation.mutateAsync(data); + closeImportDialog(); }; - const handleSavePortList = async (data: SavePortListData) => { + const handleSavePortList = async ( + data: PortListDialogSaveData, + ) => { if (isDefined(data.id)) { // save existing port list try { @@ -270,13 +270,13 @@ const PortListComponent = ({ } throw error; } - await handleSave({ + await savePortList({ id: data.id, name: data.name, comment: data.comment, }); } else { - await handleCreate(data); + await createPortList(data); } closePortListDialog(); }; @@ -360,9 +360,9 @@ const PortListComponent = ({ return ( <> {children({ - clone: handleClone, + clone: clonePortList, download: handleDownload, - delete: handleDelete, + delete: deletePortList, create: openPortListDialog, edit: openPortListDialog, import: openImportDialog, diff --git a/src/web/pages/portlists/PortListDialog.tsx b/src/web/pages/portlists/PortListDialog.tsx index 24f74eec99..51de15e74b 100644 --- a/src/web/pages/portlists/PortListDialog.tsx +++ b/src/web/pages/portlists/PortListDialog.tsx @@ -20,7 +20,7 @@ import PortRangesTable, { type PortRange, } from 'web/pages/portlists/PortRangesTable'; -export interface SavePortListData { +export interface PortListDialogSaveData { id?: string; comment: string; file?: File; @@ -41,7 +41,7 @@ interface PortListsDialogProps { title?: string; onClose?: () => void; onNewPortRangeClick?: () => void; - onSave?: (data: SavePortListData) => void | Promise; + onSave?: (data: PortListDialogSaveData) => void | Promise; onTmpDeletePortRange?: (portRange: TPortRange) => void; } diff --git a/src/web/pages/portlists/__tests__/PortListComponent.test.tsx b/src/web/pages/portlists/__tests__/PortListComponent.test.tsx index 79cef1b04d..01e32cdd54 100644 --- a/src/web/pages/portlists/__tests__/PortListComponent.test.tsx +++ b/src/web/pages/portlists/__tests__/PortListComponent.test.tsx @@ -12,6 +12,7 @@ import { wait, fireEvent, } from 'web/testing'; +import Response from 'gmp/http/response'; import PortList from 'gmp/models/port-list'; import {createSession} from 'gmp/testing'; import Button from 'web/components/form/Button'; @@ -26,20 +27,18 @@ const currentSettings = testing .mockResolvedValue(currentSettingsDefaultResponse); const createGmp = ({ getPortListResponse = defaultGetPortListResponse, - createPortListResponse = {id: '123'}, + createPortListResponse = new Response({id: '123'}), // oxlint-disable-next-line typescript/no-useless-default-assignment deletePortListResponse = undefined, - savePortListResponse = {id: '123'}, - clonePortListResponse = {id: '123'}, - createPortRangeResponse = {id: '1234'}, + savePortListResponse = new Response({id: '123'}), + clonePortListResponse = new Response({id: '123'}), + createPortRangeResponse = new Response({id: '1234'}), getPortList = testing.fn().mockResolvedValue(getPortListResponse), createPortList = testing.fn().mockResolvedValue(createPortListResponse), deletePortList = testing.fn().mockResolvedValue(deletePortListResponse), savePortList = testing.fn().mockResolvedValue(savePortListResponse), clonePortList = testing.fn().mockResolvedValue(clonePortListResponse), - createPortRange = testing - .fn() - .mockResolvedValue({data: createPortRangeResponse}), + createPortRange = testing.fn().mockResolvedValue(createPortRangeResponse), deletePortRange = testing.fn().mockResolvedValue(undefined), } = {}) => ({ portlist: { @@ -181,8 +180,8 @@ describe('PortListComponent tests', () => { const saveButton = screen.getDialogSaveButton(); fireEvent.click(saveButton); await wait(); - expect(screen.queryByText('New Port List')).not.toBeInTheDocument(); - expect(onCreateError).toHaveBeenCalledWith(error); + expect(screen.getByText('New Port List')).toBeInTheDocument(); + expect(onCreateError.mock.calls[0][0]).toBe(error); expect(onCreated).not.toHaveBeenCalled(); }); @@ -345,7 +344,6 @@ describe('PortListComponent tests', () => { expect(deletePortRange).toHaveBeenCalledExactlyOnceWith({ id: portList.portRanges[0].id, - portListId: portList.id, }); expect(onSaveError).not.toHaveBeenCalled(); expect(gmp.portlist.save).toHaveBeenCalledExactlyOnceWith({ @@ -390,9 +388,9 @@ describe('PortListComponent tests', () => { fireEvent.click(saveButton); await wait(); - expect(screen.queryByText('Edit Port List foo')).not.toBeInTheDocument(); + expect(screen.getByText('Edit Port List foo')).toBeInTheDocument(); expect(onSaved).not.toHaveBeenCalled(); - expect(onSaveError).toHaveBeenCalledExactlyOnceWith(error); + expect(onSaveError.mock.calls[0][0]).toBe(error); }); test('should show error in dialog if saving a port list fails', async () => { @@ -469,7 +467,11 @@ describe('PortListComponent tests', () => { {({clone}) => (