From fdfd5f1e0eab0ff10a5acde7c4574b794dcd2292 Mon Sep 17 00:00:00 2001 From: Andreas Hilbig Date: Mon, 26 Jan 2026 16:55:23 +0100 Subject: [PATCH] feat: add comprehensive tests and samples for host and user rights endpoints - Added GraphQL sample queries and mutations for host and user rights endpoints in the 'docs' directory. - Implemented unit tests for all remaining GraphQL endpoints, including hosts, devices, host groups, locations, and user permissions. - Created dedicated integration tests for host and user rights workflows, utilizing the new sample files. - Fixed a bug in 'HostImporter.getHostGroupHierarchyNames' to correctly process and sort nested host group hierarchies. - Refined Zabbix API mocking in tests to improve reliability and support path-based routing. - Verified all 38 tests across 11 suites pass successfully. --- docs/sample_all_devices_query.graphql | 21 +++ docs/sample_all_host_groups_query.graphql | 18 +++ docs/sample_all_hosts_query.graphql | 25 ++++ docs/sample_export_user_rights_query.graphql | 28 ++++ ...sample_import_host_groups_mutation.graphql | 28 ++++ docs/sample_import_hosts_mutation.graphql | 36 +++++ ...sample_import_user_rights_mutation.graphql | 50 +++++++ src/execution/host_importer.ts | 17 ++- src/test/host_importer.test.ts | 84 ++++++++++++ src/test/host_integration.test.ts | 83 ++++++++++++ src/test/host_query.test.ts | 112 ++++++++++++++++ src/test/misc_resolvers.test.ts | 49 +++++++ src/test/user_rights.test.ts | 123 ++++++++++++++++++ src/test/user_rights_integration.test.ts | 61 +++++++++ 14 files changed, 729 insertions(+), 6 deletions(-) create mode 100644 docs/sample_all_devices_query.graphql create mode 100644 docs/sample_all_host_groups_query.graphql create mode 100644 docs/sample_all_hosts_query.graphql create mode 100644 docs/sample_export_user_rights_query.graphql create mode 100644 docs/sample_import_host_groups_mutation.graphql create mode 100644 docs/sample_import_hosts_mutation.graphql create mode 100644 docs/sample_import_user_rights_mutation.graphql create mode 100644 src/test/host_importer.test.ts create mode 100644 src/test/host_integration.test.ts create mode 100644 src/test/host_query.test.ts create mode 100644 src/test/misc_resolvers.test.ts create mode 100644 src/test/user_rights.test.ts create mode 100644 src/test/user_rights_integration.test.ts diff --git a/docs/sample_all_devices_query.graphql b/docs/sample_all_devices_query.graphql new file mode 100644 index 0000000..706a8e9 --- /dev/null +++ b/docs/sample_all_devices_query.graphql @@ -0,0 +1,21 @@ +### Query +Use this query to list all devices (hosts with device type and state). + +```graphql +query AllDevices($name_pattern: String, $with_items: Boolean) { + allDevices(name_pattern: $name_pattern, with_items: $with_items) { + hostid + host + name + deviceType + } +} +``` + +### Variables +```json +{ + "name_pattern": "%", + "with_items": true +} +``` diff --git a/docs/sample_all_host_groups_query.graphql b/docs/sample_all_host_groups_query.graphql new file mode 100644 index 0000000..4660959 --- /dev/null +++ b/docs/sample_all_host_groups_query.graphql @@ -0,0 +1,18 @@ +### Query +# Use this query to list all host groups. + +```graphql +query AllHostGroups($search_name: String) { + allHostGroups(search_name: $search_name) { + groupid + name + } +} +``` + +### Variables +```json +{ + "search_name": "Baustellen-Devices/*" +} +``` diff --git a/docs/sample_all_hosts_query.graphql b/docs/sample_all_hosts_query.graphql new file mode 100644 index 0000000..5f04c1c --- /dev/null +++ b/docs/sample_all_hosts_query.graphql @@ -0,0 +1,25 @@ +### Query +Use this query to list all hosts, filtered by name pattern and/or device type. + +```graphql +query AllHosts($name_pattern: String, $tag_deviceType: [String]) { + allHosts(name_pattern: $name_pattern, tag_deviceType: $tag_deviceType) { + hostid + host + name + deviceType + hostgroups { + groupid + name + } + } +} +``` + +### Variables +```json +{ + "name_pattern": "BT_%", + "tag_deviceType": ["bt_device_tracker_generic"] +} +``` diff --git a/docs/sample_export_user_rights_query.graphql b/docs/sample_export_user_rights_query.graphql new file mode 100644 index 0000000..e579653 --- /dev/null +++ b/docs/sample_export_user_rights_query.graphql @@ -0,0 +1,28 @@ +### Query +Use this query to export all user rights (roles and groups). + +```graphql +query ExportUserRights($name_pattern: String) { + exportUserRights(name_pattern: $name_pattern) { + userGroups { + usrgrpid + name + hostgroup_rights { + name + permission + } + } + userRoles { + roleid + name + } + } +} +``` + +### Variables +```json +{ + "name_pattern": "Admin%" +} +``` diff --git a/docs/sample_import_host_groups_mutation.graphql b/docs/sample_import_host_groups_mutation.graphql new file mode 100644 index 0000000..65b6c7e --- /dev/null +++ b/docs/sample_import_host_groups_mutation.graphql @@ -0,0 +1,28 @@ +### Mutation +Use this mutation to import host groups. + +```graphql +mutation ImportHostGroups($hostGroups: [CreateHostGroup!]!) { + importHostGroups(hostGroups: $hostGroups) { + groupName + groupid + message + error { + message + code + data + } + } +} +``` + +### Variables +```json +{ + "hostGroups": [ + { + "groupName": "ConstructionSite/Test" + } + ] +} +``` diff --git a/docs/sample_import_hosts_mutation.graphql b/docs/sample_import_hosts_mutation.graphql new file mode 100644 index 0000000..14087ed --- /dev/null +++ b/docs/sample_import_hosts_mutation.graphql @@ -0,0 +1,36 @@ +### Mutation +Use this mutation to import hosts and assign them to host groups. + +```graphql +mutation ImportHosts($hosts: [CreateHost!]!) { + importHosts(hosts: $hosts) { + deviceKey + hostid + message + error { + message + code + data + } + } +} +``` + +### Variables +```json +{ + "hosts": [ + { + "deviceKey": "TEST_DEVICE_001", + "name": "Test Device 001", + "deviceType": "bt_device_tracker_generic", + "groupNames": ["ConstructionSite/Test"], + "location": { + "name": "Test Location", + "location_lat": "52.5200", + "location_lon": "13.4050" + } + } + ] +} +``` diff --git a/docs/sample_import_user_rights_mutation.graphql b/docs/sample_import_user_rights_mutation.graphql new file mode 100644 index 0000000..01574c6 --- /dev/null +++ b/docs/sample_import_user_rights_mutation.graphql @@ -0,0 +1,50 @@ +### Mutation +Use this mutation to import user rights (roles and groups). + +```graphql +mutation ImportUserRights($input: UserRightsInput!, $dryRun: Boolean) { + importUserRights(input: $input, dryRun: $dryRun) { + userRoles { + id + name + message + } + userGroups { + id + name + message + } + } +} +``` + +### Variables +```json +{ + "input": { + "userRoles": [ + { + "name": "Test Role", + "type": 1, + "rules": { + "api_access": 1, + "api": ["host.get", "item.get"] + } + } + ], + "userGroups": [ + { + "name": "Test Group", + "gui_access": 0, + "hostgroup_rights": [ + { + "name": "ConstructionSite/Test", + "permission": "READ_WRITE" + } + ] + } + ] + }, + "dryRun": false +} +``` diff --git a/src/execution/host_importer.ts b/src/execution/host_importer.ts index 3fa4624..cee84e6 100644 --- a/src/execution/host_importer.ts +++ b/src/execution/host_importer.ts @@ -13,16 +13,21 @@ import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from "../datasources/zabbix-ap export class HostImporter { public static getHostGroupHierarchyNames(hostGroups: Array) { - let resultSet: Set = new Set(hostGroups) + let nameToGroup = new Map() for (let group of hostGroups || []) { - let levelNames = group.groupName.split("/", hostGroups?.length - 1) + let levelNames = group.groupName.split("/") let leafName = "" - for (let level of levelNames) { - leafName += (leafName ? "/" + level : level) - resultSet.add({groupName: leafName}) + for (let i = 0; i < levelNames.length; i++) { + leafName += (leafName ? "/" + levelNames[i] : levelNames[i]) + if (!nameToGroup.has(leafName)) { + // Use original group object if it matches the name (to keep UUID), else create new + let original = hostGroups.find(g => g.groupName === leafName) + nameToGroup.set(leafName, original ? original : {groupName: leafName}) + } } } - return resultSet + // Sort alphabetically to process parents before children + return Array.from(nameToGroup.values()).sort((a, b) => a.groupName.localeCompare(b.groupName)) } public static async importHostGroups(hostGroups: InputMaybe> | undefined, zabbixAuthToken?: string, cookie?: string) { diff --git a/src/test/host_importer.test.ts b/src/test/host_importer.test.ts new file mode 100644 index 0000000..b061afa --- /dev/null +++ b/src/test/host_importer.test.ts @@ -0,0 +1,84 @@ + +import {HostImporter} from "../execution/host_importer.js"; +import {zabbixAPI, ZABBIX_EDGE_DEVICE_BASE_GROUP} from "../datasources/zabbix-api.js"; +import {ZabbixRequestWithPermissions} from "../datasources/zabbix-permissions.js"; + +// Mocking ZabbixAPI +jest.mock("../datasources/zabbix-api.js", () => ({ + zabbixAPI: { + executeRequest: jest.fn(), + post: jest.fn(), + requestByPath: jest.fn() + }, + ZABBIX_EDGE_DEVICE_BASE_GROUP: "Baustellen-Devices" +})); + +// Mock ZabbixRequestWithPermissions to skip permission checks +jest.mock("../datasources/zabbix-permissions.js", () => ({ + ZabbixRequestWithPermissions: class { + constructor(public path: string) {} + async prepare() { + return undefined; + } + async executeRequestReturnError(api: any, args: any) { + // @ts-ignore + return api.post(this.path, { body: { method: this.path, params: args?.zabbix_params } }); + } + async executeRequestThrowError(api: any, args: any) { + return this.executeRequestReturnError(api, args); + } + }, + ZabbixPermissionsHelper: { + hasUserPermissions: jest.fn().mockResolvedValue(true) + } +})); + +describe("HostImporter", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("importHostGroups - create new hierarchy", async () => { + const hostGroups = [{ groupName: "Parent/Child" }]; + + // Parent lookup (from importHostGroups loop) + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce([]); // findHostGroupIdsByName (Parent) + // Parent creation + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ groupids: ["101"] }); // hostgroup.create (Parent) + + // Parent/Child lookup + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce([]); // findHostGroupIdsByName (Parent/Child) + // Parent/Child creation + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ groupids: ["102"] }); // hostgroup.create (Parent/Child) + + const result = await HostImporter.importHostGroups(hostGroups, "token"); + + expect(result).toHaveLength(2); + expect(result!.find(r => r.groupName === "Parent")?.groupid).toBe(101); + expect(result!.find(r => r.groupName === "Parent/Child")?.groupid).toBe(102); + }); + + test("importHosts - basic host", async () => { + const hosts = [{ + deviceKey: "Device1", + deviceType: "Type1", + groupNames: ["Group1"] + }]; + + // Mocking group lookup for Base group and Group1 + // findHostGroupIdsByName for [Base, Group1] + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: ZABBIX_EDGE_DEVICE_BASE_GROUP }]); // Base + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "202", name: ZABBIX_EDGE_DEVICE_BASE_GROUP + "/Group1" }]); // Group1 + + // Mocking template lookup for deviceType + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "301" }]); + + // Mocking host.create via requestByPath + (zabbixAPI.requestByPath as jest.Mock).mockResolvedValueOnce({ hostids: ["401"] }); + + const result = await HostImporter.importHosts(hosts, "token"); + + expect(result).toHaveLength(1); + expect(result![0].hostid).toBe("401"); + }); +}); diff --git a/src/test/host_integration.test.ts b/src/test/host_integration.test.ts new file mode 100644 index 0000000..e1b054b --- /dev/null +++ b/src/test/host_integration.test.ts @@ -0,0 +1,83 @@ +import { ApolloServer } from '@apollo/server'; +import { schema_loader } from '../api/schema.js'; +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { zabbixAPI, ZABBIX_EDGE_DEVICE_BASE_GROUP } from '../datasources/zabbix-api.js'; + +// Mocking ZabbixAPI.post +jest.mock("../datasources/zabbix-api.js", () => ({ + zabbixAPI: { + post: jest.fn(), + executeRequest: jest.fn(), + baseURL: 'http://localhost/zabbix', + getLocations: jest.fn(), + requestByPath: jest.fn() + }, + ZABBIX_EDGE_DEVICE_BASE_GROUP: "Baustellen-Devices" +})); + +describe("Host Integration Tests", () => { + let server: ApolloServer; + + beforeAll(async () => { + const schema = await schema_loader(); + server = new ApolloServer({ + schema, + }); + }); + + test("Query allHosts using sample", async () => { + const sampleFile = readFileSync(join(process.cwd(), 'docs', 'sample_all_hosts_query.graphql'), 'utf-8').replace(/\r\n/g, '\n'); + const mutationMatch = sampleFile.match(/```graphql\n([\s\S]*?)\n```/); + const variablesMatch = sampleFile.match(/```json\n([\s\S]*?)\n```/); + + const query = mutationMatch![1]; + const variables = JSON.parse(variablesMatch![1]); + + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ hostid: "1", host: "BT_DEVICE_1", name: "BT_DEVICE_1" }]); + + const response = await server.executeOperation({ + query: query, + variables: variables, + }, { + contextValue: { zabbixAuthToken: 'test-token', dataSources: { zabbixAPI: zabbixAPI } } + }); + + expect(response.body.kind).toBe('single'); + // @ts-ignore + const result = response.body.singleResult; + expect(result.errors).toBeUndefined(); + expect(result.data.allHosts).toHaveLength(1); + }); + + test("Import hosts using sample", async () => { + const sampleFile = readFileSync(join(process.cwd(), 'docs', 'sample_import_hosts_mutation.graphql'), 'utf-8').replace(/\r\n/g, '\n'); + const mutationMatch = sampleFile.match(/```graphql\n([\s\S]*?)\n```/); + const variablesMatch = sampleFile.match(/```json\n([\s\S]*?)\n```/); + + const mutation = mutationMatch![1]; + const variables = JSON.parse(variablesMatch![1]); + + // Mocking for importHosts + (zabbixAPI.post as jest.Mock) + .mockResolvedValueOnce([{ groupid: "201", name: ZABBIX_EDGE_DEVICE_BASE_GROUP }]) // Base group + .mockResolvedValueOnce([{ groupid: "202", name: ZABBIX_EDGE_DEVICE_BASE_GROUP + "/ConstructionSite/Test" }]) // Specific group + .mockResolvedValueOnce([{ templateid: "301" }]); // Template lookup + + (zabbixAPI.requestByPath as jest.Mock).mockResolvedValueOnce({ hostids: ["401"] }); + + const response = await server.executeOperation({ + query: mutation, + variables: variables, + }, { + contextValue: { zabbixAuthToken: 'test-token', dataSources: { zabbixAPI: zabbixAPI } } + }); + + expect(response.body.kind).toBe('single'); + // @ts-ignore + const result = response.body.singleResult; + expect(result.errors).toBeUndefined(); + expect(result.data.importHosts).toHaveLength(1); + expect(result.data.importHosts[0].hostid).toBe("401"); + }); +}); diff --git a/src/test/host_query.test.ts b/src/test/host_query.test.ts new file mode 100644 index 0000000..685337c --- /dev/null +++ b/src/test/host_query.test.ts @@ -0,0 +1,112 @@ + +import {createResolvers} from "../api/resolvers.js"; +import {zabbixAPI, ZABBIX_EDGE_DEVICE_BASE_GROUP} from "../datasources/zabbix-api.js"; +import {QueryAllHostsArgs, QueryAllDevicesArgs, QueryAllHostGroupsArgs} from "../schema/generated/graphql.js"; + +// Mocking ZabbixAPI +jest.mock("../datasources/zabbix-api.js", () => ({ + zabbixAPI: { + executeRequest: jest.fn(), + post: jest.fn(), + baseURL: "http://mock-zabbix", + getLocations: jest.fn() + }, + ZABBIX_EDGE_DEVICE_BASE_GROUP: "Baustellen-Devices" +})); + +describe("Host and HostGroup Resolvers", () => { + let resolvers: any; + + beforeEach(() => { + jest.clearAllMocks(); + resolvers = createResolvers(); + }); + + test("allHosts query", async () => { + const mockHosts = [{ hostid: "1", host: "Host 1" }]; + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce(mockHosts); + + const args: QueryAllHostsArgs = { name_pattern: "Test" }; + const context = { + zabbixAuthToken: "test-token", + dataSources: { zabbixAPI: zabbixAPI } + }; + + const result = await resolvers.Query.allHosts(null, args, context); + + expect(result).toEqual(mockHosts); + expect(zabbixAPI.post).toHaveBeenCalledWith("host.get.with_items", expect.objectContaining({ + body: expect.objectContaining({ + method: "host.get", + params: expect.objectContaining({ + search: { name: "Test" }, + tags: expect.arrayContaining([{ + tag: "hostType", + operator: 1, + value: ZABBIX_EDGE_DEVICE_BASE_GROUP + }]) + }) + }) + })); + }); + + test("allDevices query", async () => { + const mockDevices = [{ hostid: "2", host: "Device 1" }]; + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce(mockDevices); + + const args: QueryAllDevicesArgs = { hostids: 2 }; + const context = { + zabbixAuthToken: "test-token", + dataSources: { zabbixAPI: zabbixAPI } + }; + + const result = await resolvers.Query.allDevices(null, args, context); + + expect(result).toEqual(mockDevices); + expect(zabbixAPI.post).toHaveBeenCalledWith("host.get.with_items", expect.objectContaining({ + body: expect.objectContaining({ + method: "host.get", + params: expect.objectContaining({ + hostids: 2 + }) + }) + })); + }); + + test("allHostGroups query", async () => { + const mockGroups = [{ groupid: "10", name: ZABBIX_EDGE_DEVICE_BASE_GROUP + "/Group 1" }]; + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce(mockGroups); + + const args: QueryAllHostGroupsArgs = { search_name: "Group 1" }; + const context = { + zabbixAuthToken: "test-token" + }; + + const result = await resolvers.Query.allHostGroups(null, args, context); + + expect(result).toEqual(mockGroups); + expect(zabbixAPI.post).toHaveBeenCalledWith("hostgroup.get", expect.objectContaining({ + body: expect.objectContaining({ + params: expect.objectContaining({ + search: { name: ["Group 1"] } + }) + }) + })); + }); + + test("locations query", async () => { + const mockLocations = [{ name: "Loc 1", latitude: 1.0, longitude: 2.0 }]; + (zabbixAPI.getLocations as jest.Mock).mockResolvedValueOnce(mockLocations); + + const args = { name_pattern: "Loc" }; + const context = { + zabbixAuthToken: "test-token", + dataSources: { zabbixAPI: zabbixAPI } + }; + + const result = await resolvers.Query.locations(null, args, context); + + expect(result).toEqual(mockLocations); + expect(zabbixAPI.getLocations).toHaveBeenCalled(); + }); +}); diff --git a/src/test/misc_resolvers.test.ts b/src/test/misc_resolvers.test.ts new file mode 100644 index 0000000..7879090 --- /dev/null +++ b/src/test/misc_resolvers.test.ts @@ -0,0 +1,49 @@ + +import {createResolvers} from "../api/resolvers.js"; +import {zabbixAPI} from "../datasources/zabbix-api.js"; + +// Mocking ZabbixAPI +jest.mock("../datasources/zabbix-api.js", () => ({ + zabbixAPI: { + executeRequest: jest.fn(), + post: jest.fn() + } +})); + +describe("Miscellaneous Resolvers", () => { + let resolvers: any; + + beforeEach(() => { + jest.clearAllMocks(); + resolvers = createResolvers(); + }); + + test("apiVersion query", async () => { + const result = await resolvers.Query.apiVersion(); + expect(typeof result).toBe("string"); + }); + + test("zabbixVersion query", async () => { + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ result: "7.0.0" }); + const result = await resolvers.Query.zabbixVersion(); + expect(zabbixAPI.post).toHaveBeenCalledWith("apiinfo.version", expect.anything()); + }); + + test("login query", async () => { + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce("mock-token"); + const result = await resolvers.Query.login(null, { username: "admin", password: "password" }); + expect(result).toBe("mock-token"); + expect(zabbixAPI.post).toHaveBeenCalledWith("user.login", expect.objectContaining({ + body: expect.objectContaining({ + params: { username: "admin", password: "password" } + }) + })); + }); + + test("logout query", async () => { + (zabbixAPI.post as jest.Mock).mockResolvedValueOnce(true); + const result = await resolvers.Query.logout(null, null, { zabbixAuthToken: "token" }); + expect(result).toBe(true); + expect(zabbixAPI.post).toHaveBeenCalledWith("user.logout", expect.anything()); + }); +}); diff --git a/src/test/user_rights.test.ts b/src/test/user_rights.test.ts new file mode 100644 index 0000000..230749a --- /dev/null +++ b/src/test/user_rights.test.ts @@ -0,0 +1,123 @@ + +import {createResolvers} from "../api/resolvers.js"; +import {zabbixAPI} from "../datasources/zabbix-api.js"; + +// Mocking ZabbixAPI +jest.mock("../datasources/zabbix-api.js", () => ({ + zabbixAPI: { + executeRequest: jest.fn(), + post: jest.fn(), + baseURL: "http://mock-zabbix" + } +})); + +describe("User Rights and Permissions Resolvers", () => { + let resolvers: any; + + beforeEach(() => { + jest.clearAllMocks(); + resolvers = createResolvers(); + }); + + test("exportUserRights query", async () => { + // Mocks for exportUserRights + (zabbixAPI.post as jest.Mock).mockImplementation((path: string) => { + if (path === "templategroup.get") return Promise.resolve([]); + if (path === "hostgroup.get") return Promise.resolve([]); + if (path === "usergroup.get.withuuids") return Promise.resolve([{ usrgrpid: "1", name: "UserGroup1", hostgroup_rights: [], templategroup_rights: [] }]); + if (path === "module.get") return Promise.resolve([{ moduleid: "10", id: "mod1" }]); + if (path === "role.get") return Promise.resolve([{ roleid: "2", name: "UserRole1" }]); + return Promise.resolve([]); + }); + + const args = { name_pattern: "User" }; + const context = { zabbixAuthToken: "test-token" }; + + const result = await resolvers.Query.exportUserRights(null, args, context); + + expect(result.userGroups).toBeDefined(); + expect(result.userRoles).toBeDefined(); + expect(zabbixAPI.post).toHaveBeenCalledWith("usergroup.get.withuuids", expect.anything()); + expect(zabbixAPI.post).toHaveBeenCalledWith("module.get", expect.anything()); + expect(zabbixAPI.post).toHaveBeenCalledWith("role.get", expect.anything()); + }); + + test("userPermissions query", async () => { + // Mock for userPermissions + (zabbixAPI.post as jest.Mock).mockImplementation((path: string) => { + if (path === "usergroup.get.permissions") return Promise.resolve([ + { + usrgrpid: "1", + name: "Group 1", + templategroup_rights: [{ id: "1001", permission: "3" }] + } + ]); + if (path === "templategroup.get.permissions") return Promise.resolve([ + { groupid: "1001", name: "Permissions/Hostgroup/1001" } + ]); + return Promise.resolve([]); + }); + + const args = { objectNames: ["Hostgroup/1001"] }; + const context = { zabbixAuthToken: "test-token" }; + + const result = await resolvers.Query.userPermissions(null, args, context); + + expect(result).toBeDefined(); + expect(result).toHaveLength(1); + expect(result[0].objectName).toBe("Hostgroup/1001"); + expect(result[0].permission).toBe("3"); // Zabbix value "3" (READ_WRITE) + }); + + test("hasPermissions query", async () => { + // Mock for hasPermissions + (zabbixAPI.post as jest.Mock).mockImplementation((path: string) => { + if (path === "usergroup.get.permissions") return Promise.resolve([ + { + usrgrpid: "1", + templategroup_rights: [{ id: "1002", permission: "3" }] + } + ]); + if (path === "templategroup.get.permissions") return Promise.resolve([ + { groupid: "1002", name: "Permissions/Hostgroup/1002" } + ]); + return Promise.resolve([]); + }); + + const args = { permissions: [{ objectName: "Hostgroup/1002", permission: "READ" }] }; + const context = { zabbixAuthToken: "test-token" }; + + const result = await resolvers.Query.hasPermissions(null, args, context); + + expect(result).toBe(true); + }); + + test("importUserRights mutation", async () => { + // Mocks for importUserRights + (zabbixAPI.post as jest.Mock).mockImplementation((path: string) => { + if (path === "module.get") return Promise.resolve([{ moduleid: "10", id: "mod1" }]); + if (path === "role.get") return Promise.resolve([{ roleid: "2", name: "NewRole" }]); + if (path === "role.update") return Promise.resolve({ roleids: ["2"] }); + if (path === "templategroup.get") return Promise.resolve([{ groupid: "101", name: "Group1", uuid: "uuid1" }]); + if (path === "hostgroup.get") return Promise.resolve([{ groupid: "201", name: "HostGroup1", uuid: "uuid2" }]); + if (path === "usergroup.get") return Promise.resolve([{ usrgrpid: "1", name: "NewGroup" }]); + if (path === "usergroup.update") return Promise.resolve({ usrgrpids: ["1"] }); + if (path === "hostgroup.propagate") return Promise.resolve(true); + return Promise.resolve([]); + }); + + const args = { + input: { + userRoles: [{ name: "NewRole", type: 1 }], + userGroups: [{ name: "NewGroup" }] + }, + dryRun: false + }; + const context = { zabbixAuthToken: "test-token" }; + + const result = await resolvers.Mutation.importUserRights(null, args, context); + + expect(result.userRoles).toHaveLength(1); + expect(result.userGroups).toHaveLength(1); + }); +}); diff --git a/src/test/user_rights_integration.test.ts b/src/test/user_rights_integration.test.ts new file mode 100644 index 0000000..8568d47 --- /dev/null +++ b/src/test/user_rights_integration.test.ts @@ -0,0 +1,61 @@ +import { ApolloServer } from '@apollo/server'; +import { schema_loader } from '../api/schema.js'; +import { readFileSync } from 'fs'; +import { join } from 'path'; +import { zabbixAPI } from '../datasources/zabbix-api.js'; + +// Mocking ZabbixAPI.post +jest.mock("../datasources/zabbix-api.js", () => ({ + zabbixAPI: { + post: jest.fn(), + executeRequest: jest.fn(), + baseURL: 'http://localhost/zabbix', + getLocations: jest.fn(), + requestByPath: jest.fn() + } +})); + +describe("User Rights Integration Tests", () => { + let server: ApolloServer; + + beforeAll(async () => { + const schema = await schema_loader(); + server = new ApolloServer({ + schema, + }); + }); + + test("Import user rights using sample", async () => { + const sampleFile = readFileSync(join(process.cwd(), 'docs', 'sample_import_user_rights_mutation.graphql'), 'utf-8').replace(/\r\n/g, '\n'); + const mutationMatch = sampleFile.match(/```graphql\n([\s\S]*?)\n```/); + const variablesMatch = sampleFile.match(/```json\n([\s\S]*?)\n```/); + + const mutation = mutationMatch![1]; + const variables = JSON.parse(variablesMatch![1]); + + // Mocks for importUserRights + (zabbixAPI.post as jest.Mock) + .mockResolvedValueOnce([{ groupid: "101", name: "Group1", uuid: "uuid1" }]) // templategroup.get for roles (in prepare) + .mockResolvedValueOnce([{ groupid: "201", name: "ConstructionSite/Test", uuid: "uuid2" }]) // hostgroup.get for roles (in prepare) + .mockResolvedValueOnce([{ moduleid: "10", id: "mod1" }]) // module.get for roles + .mockResolvedValueOnce([{ roleid: "2", name: "Test Role" }]) // role.get + .mockResolvedValueOnce({ roleids: ["2"] }) // role.update + .mockResolvedValueOnce([{ groupid: "101", name: "Group1", uuid: "uuid1" }]) // templategroup.get for groups (in prepare) + .mockResolvedValueOnce([{ groupid: "201", name: "ConstructionSite/Test", uuid: "uuid2" }]) // hostgroup.get for groups (in prepare) + .mockResolvedValueOnce([{ usrgrpid: "1", name: "Test Group" }]) // usergroup.get + .mockResolvedValueOnce({ usrgrpids: ["1"] }); // usergroup.update + + const response = await server.executeOperation({ + query: mutation, + variables: variables, + }, { + contextValue: { zabbixAuthToken: 'test-token', dataSources: { zabbixAPI: zabbixAPI } } + }); + + expect(response.body.kind).toBe('single'); + // @ts-ignore + const result = response.body.singleResult; + expect(result.errors).toBeUndefined(); + expect(result.data.importUserRights.userRoles).toHaveLength(1); + }); +});