diff --git a/.gitignore b/.gitignore
index 78ded7b..a612f5d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,10 +74,7 @@ web_modules/
# dotenv environment variable files
.env
-.env.development.local
-.env.test.local
-.env.production.local
-.env.local
+.env.*
# parcel-bundler cache (https://parceljs.org/)
.cache
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index d1b9822..6696220 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -5,30 +5,17 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
@@ -39,7 +26,7 @@
-
+
@@ -103,7 +90,7 @@
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
- "git-widget-placeholder": "main",
+ "git-widget-placeholder": "feature-improve-zabbix-version-compatiblity",
"go.import.settings.migrated": "true",
"javascript.preferred.runtime.type.id": "node",
"junie.onboarding.icon.badge.shown": "true",
@@ -124,6 +111,11 @@
"to.speed.mode.migration.done": "true",
"ts.external.directory.path": "\\\\wsl.localhost\\Ubuntu\\home\\ahilbig\\git\\vcr\\zabbix-graphql-api\\node_modules\\typescript\\lib",
"vue.rearranger.settings.migration": "true"
+ },
+ "keyToStringList": {
+ "com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
+ "TEXT"
+ ]
}
}]]>
@@ -231,7 +223,10 @@
-
+
+
+
+
@@ -496,12 +491,12 @@
file://$PROJECT_DIR$/src/datasources/zabbix-request.ts
- 134
+ 135
file://$PROJECT_DIR$/src/datasources/zabbix-request.ts
- 254
+ 276
diff --git a/README.md b/README.md
index 9fd837f..ef4593c 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ Before you begin, ensure you have met the following requirements:
- **Node.js**: Version 24 (LTS) or higher recommended.
- **Docker**: Version 27 or higher and **Docker Compose** v2.29 or higher (use `docker compose` instead of `docker-compose`).
-- **Zabbix**: A running Zabbix instance (compatible with Zabbix 6.0+) with API access.
+- **Zabbix**: A running Zabbix instance (**Zabbix 6.2+ mandatory**) with API access. See [Zabbix Version Compatibility](#-zabbix-version-compatibility) for details.
- **Zabbix Super Admin Token** (for full functionality / privilege escalation).
- **Zabbix User Access** (groups and roles depending on your use case).
@@ -81,6 +81,20 @@ Builds the project and runs the compiled code:
npm run prod
```
+#### ๐ณ Local Zabbix Environment
+For development and testing, you can start a complete environment including Zabbix from scratch:
+
+```bash
+# Start with local Zabbix (latest 7.0)
+docker compose --profile zabbix-local up -d
+
+# Start with a specific Zabbix version (e.g. 6.4)
+ZABBIX_VERSION=alpine-6.4-latest docker compose --profile zabbix-local up -d
+```
+
+> **Guide**: For detailed setup and configuration of the local environment, see [Local Development Environment](./docs/howtos/local_development.md).
+> **Important**: On fresh Zabbix installations, you must manually create the base host group (e.g., `Roadwork`) before the API can import devices.
+
The API will be available at `http://localhost:4000/`.
## โ๏ธ Configuration
@@ -236,7 +250,27 @@ The API version is automatically set during the Docker build process based on th
### ๐ง Zabbix Version Compatibility
-This API is designed to work with Zabbix 7.4, which is the version it runs productively with. While it may work with earlier versions (like 6.0+), 7.4 is the officially supported and tested version.
+This API is officially supported and productively used with **Zabbix 7.0 (LTS)**, **7.4**, and newer. It maintains compatibility with **Zabbix 6.4** and **6.2**, with the following version-specific behaviors:
+
+- **Zabbix 7.0+ (including 7.4)**:
+ - Full feature support.
+ - **History Push**: Uses the native `history.push` API for efficient data ingestion.
+- **Zabbix 6.4**:
+ - **History Push**: Not supported (requires Zabbix 7.0+). The `pushHistory` mutation returns a clear error.
+ - **Group Propagation**: Fully supported via the `hostgroup.propagate` API.
+ - **UUIDs**: Fully supported for both Host Groups and Template Groups.
+- **Zabbix 6.2**:
+ - **History Push**: Not supported.
+ - **Authentication**: Fully supported. The API automatically falls back to using the `auth` field in JSON-RPC request bodies since Bearer token headers were only introduced in 6.4.
+
+#### โ ๏ธ Dropped Support for Zabbix 6.0
+Support for Zabbix 6.0 (LTS) has been discontinued due to the excessive complexity of maintaining backward compatibility with its legacy API structure. The high amount of differences between 6.0 and 6.2 would have required several intrusive fallbacks:
+- **API Methods**: A translation layer to redirect `templategroup.*` calls to `hostgroup.*` methods, as these entities were not yet separated.
+- **Permission Management**: Manual recursive expansion of group rights during import because `hostgroup.propagate` was unavailable.
+- **Entity Matching**: Unreliable name-based fallback for host groups due to the lack of UUID support in the 6.0 API.
+- **JSON-RPC**: Complexity in restoring the `auth` field in request bodies for versions lacking modern Bearer token header support.
+
+By requiring **Zabbix 6.2+**, the API leverages native modern features, ensuring higher reliability and a more maintainable codebase.
## ๐ ๏ธ Technical Maintenance
diff --git a/docker-compose.yml b/docker-compose.yml
index 4a56b5c..c938d1f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -43,5 +43,50 @@ services:
- mcp-shared:/mcp-data
command: sh -c "cat /schema/*.graphql > /mcp-data/schema.graphql"
+ postgres-server:
+ image: postgres:16-alpine
+ profiles:
+ - zabbix-local
+ environment:
+ - POSTGRES_USER=zabbix
+ - POSTGRES_PASSWORD=zabbix
+ - POSTGRES_DB=zabbix
+ volumes:
+ - zbx_db_data:/var/lib/postgresql/data
+
+ zabbix-server:
+ image: zabbix/zabbix-server-pgsql:${ZABBIX_VERSION:-alpine-7.0-latest}
+ profiles:
+ - zabbix-local
+ ports:
+ - "10051:10051"
+ environment:
+ - DB_SERVER_HOST=postgres-server
+ - POSTGRES_USER=zabbix
+ - POSTGRES_PASSWORD=zabbix
+ - POSTGRES_DB=zabbix
+ - ZBX_ALLOWUNSUPPORTEDDBVERSIONS=1
+ depends_on:
+ - postgres-server
+
+ zabbix-web:
+ image: zabbix/zabbix-web-nginx-pgsql:${ZABBIX_VERSION:-alpine-7.0-latest}
+ profiles:
+ - zabbix-local
+ ports:
+ - "8080:8080"
+ environment:
+ - ZBX_SERVER_HOST=zabbix-server
+ - DB_SERVER_HOST=postgres-server
+ - POSTGRES_USER=zabbix
+ - POSTGRES_PASSWORD=zabbix
+ - POSTGRES_DB=zabbix
+ - PHP_TZ=UTC
+ - ZBX_ALLOWUNSUPPORTEDDBVERSIONS=1
+ depends_on:
+ - postgres-server
+ - zabbix-server
+
volumes:
mcp-shared:
+ zbx_db_data:
diff --git a/docs/howtos/README.md b/docs/howtos/README.md
index 43ed518..d619569 100644
--- a/docs/howtos/README.md
+++ b/docs/howtos/README.md
@@ -22,6 +22,9 @@ Discover how the permission system works, how to define permission levels using
### ๐ ๏ธ [Technical Maintenance](./maintenance.md)
Guide on code generation (GraphQL Codegen), running Jest tests, and local Docker builds.
+### ๐ป [Local Development Environment](./local_development.md)
+Detailed instructions for setting up a fully isolated local development environment with Zabbix using Docker Compose.
+
### ๐งช [Test Specification](../tests.md)
Detailed list of test cases, categories (Unit, Integration, E2E), and coverage checklist.
diff --git a/docs/howtos/local_development.md b/docs/howtos/local_development.md
new file mode 100644
index 0000000..b906966
--- /dev/null
+++ b/docs/howtos/local_development.md
@@ -0,0 +1,88 @@
+# ๐ป Local Development Environment
+
+This guide provides detailed instructions on how to set up a fully isolated local development environment for the Zabbix GraphQL API using Docker Compose.
+
+## ๐ Overview
+The project includes a Docker Compose profile that launches a complete Zabbix stack alongside the GraphQL API and MCP server. This allows you to develop and test features against different Zabbix versions without needing an external Zabbix installation.
+
+### Included Services
+- **`zabbix-graphql-api`**: The main GraphQL server.
+- **`apollo-mcp-server`**: The Model Context Protocol server for AI integration.
+- **`postgres-server`**: PostgreSQL 16 database for Zabbix.
+- **`zabbix-server`**: Zabbix Server (PostgreSQL version).
+- **`zabbix-web`**: Zabbix Web interface (Nginx/PostgreSQL).
+
+## ๐ ๏ธ Step-by-Step Setup
+
+### 1. Start the Environment
+Use the `zabbix-local` profile to start all services. You can optionally specify the Zabbix version using the `ZABBIX_VERSION` environment variable.
+
+```bash
+# Start the latest 7.0 (default)
+docker compose --profile zabbix-local up -d
+
+# Start with a specific Zabbix version (e.g. 6.4)
+ZABBIX_VERSION=alpine-6.4-latest docker compose --profile zabbix-local up -d
+```
+
+### 2. Configure Zabbix
+Once the containers are running, you need to perform a minimal setup in the Zabbix UI:
+
+1. Open `http://localhost:8080` in your browser.
+2. Log in with the default credentials:
+ - **User**: `Admin`
+ - **Password**: `zabbix`
+3. Navigate to **Administration** > **Users** > **API tokens**.
+4. Create a new token for the `Admin` user (or a dedicated service user) and copy it.
+5. **Create Base Host Group**: The API requires a base host group to exist for importing devices.
+ - Navigate to **Configuration** > **Host groups** (or **Data collection** > **Host groups** in Zabbix 7.0).
+ - Create a host group named `Roadwork` (or the value of `ZABBIX_EDGE_DEVICE_BASE_GROUP` in your `.env`).
+ - **Note**: If your base group is nested (e.g., `Roadwork/Devices`), ensure the full path exists. Zabbix does not automatically create parent groups when creating child groups via the API.
+
+### 3. Connect the API
+To connect the GraphQL API to your local Zabbix instance, update your `.env` file with the following values (referenced in `samples/zabbix-local.env`):
+
+```env
+# Internal Docker network URL
+ZABBIX_BASE_URL=http://zabbix-web:8080/
+
+# The token you created in Step 2
+ZABBIX_PRIVILEGE_ESCALATION_TOKEN=your-newly-created-token
+```
+
+### 4. Restart the API
+Apply the configuration by restarting the API container:
+
+```bash
+docker compose restart zabbix-graphql-api
+```
+
+## โ
Verification
+You can verify the setup by running a simple query against the local API:
+
+```bash
+curl -X POST -H "Content-Type: application/json" \
+ -d '{"query": "{ apiVersion zabbixVersion }"}' \
+ http://localhost:4001/
+```
+
+The `zabbixVersion` should match the version you specified in `ZABBIX_VERSION`.
+
+## ๐ก Advanced Usage
+
+### Data Persistence
+Zabbix data is stored in a named Docker volume called `zbx_db_data`. This ensures that your configuration, templates, and hosts are preserved even if you stop or remove the containers.
+
+To perform a clean reset of the environment:
+```bash
+docker compose --profile zabbix-local down -v
+```
+
+### Testing Multiple Versions
+The local environment is perfect for testing the API's version-specific logic (e.g. `history.push` vs `zabbix_sender`). Simply change the `ZABBIX_VERSION` variable and restart the stack.
+
+Supported tags can be found on the [Zabbix Docker Hub](https://hub.docker.com/u/zabbix).
+
+---
+**Related Guide**: [Technical Maintenance](./maintenance.md)
+**Related Reference**: [Zabbix Version Compatibility](../../README.md#-zabbix-version-compatibility)
diff --git a/docs/howtos/maintenance.md b/docs/howtos/maintenance.md
index 2d9b7cd..39f1405 100644
--- a/docs/howtos/maintenance.md
+++ b/docs/howtos/maintenance.md
@@ -34,6 +34,9 @@ We use [Jest](https://jestjs.io/) for unit and integration testing.
npm run test
```
+#### Local Development Setup
+For running integration tests against a real Zabbix instance, it is recommended to use the [Local Development Environment](./local_development.md). This setup allows you to test the API against specific Zabbix versions (e.g. 6.4, 7.0) in an isolated way.
+
#### Adding New Tests
- **Location**: Place new test files in `src/test/` with the `.test.ts` extension.
- **Coverage**: Ensure you cover both successful operations and error scenarios.
diff --git a/roadmap.md b/roadmap.md
index 4abf77e..9d1260f 100644
--- a/roadmap.md
+++ b/roadmap.md
@@ -6,12 +6,21 @@ This document outlines the achieved milestones and planned enhancements for the
- **๐ฏ VCR Product Integration**: Developed a specialized **GraphQL API** as part of the VCR Product to enable the use of **Zabbix** as a robust base for monitoring and controlling **IoT devices**.
- *First use case*: Control of mobile traffic jam warning installations on **German Autobahns**.
+- **โก Query Optimization**: Optimized GraphQL API queries to reduce the amount of data fetched from Zabbix depending on the fields really requested.
+ - *Implementation*: Added dynamic output selection and field pruning in `ZabbixRequest`.
+
- **๐ Open Source Extraction & AI Integration**: Extracted the core functionality of the API to publish it as an **Open Source** project.
- *AI Integration*: Enhanced with **Model Context Protocol (MCP)** and **AI agent** integration to enable workflow and agent-supported use cases.
-## ๐
Planned Enhancements
-- **โก Query Optimization**: Optimize GraphQL API queries to reduce the amount of data fetched from Zabbix depending on the fields really requested and improve performance.
+- **๐ณ Local Development Environment**: Integrated a complete Zabbix stack into the Docker Compose configuration using profiles.
+ - *Feature*: Support for multiple Zabbix versions (6.2, 6.4, 7.0+) for development and testing.
+ - *Implementation*: Added `zabbix-local` profile and `ZABBIX_VERSION` dynamic image tagging.
+- **๐ง Multi-Version Compatibility**: Verified and enhanced support for Zabbix 6.2, 6.4, and 7.0 LTS.
+ - *Feature*: Native support for separate template groups and automated permission propagation.
+ - *Verification*: Full regression and smoketest suites passed across all mentioned versions. Support for Zabbix 6.0 was deprecated due to excessive API differences and required fallbacks.
+
+## ๐
Planned Enhancements
- **๐๏ธ Trade Fair Logistics Use Case**: Extend the API to support trade fair logistics use cases by analyzing requirements from business stakeholders.
- *Analysis*: Analysis of "Trade Fair Logistics" and derived [requirements document](docs/use-cases/trade-fair-logistics-requirements.md).
- *Simulation*:
diff --git a/samples/zabbix-local.env b/samples/zabbix-local.env
new file mode 100644
index 0000000..b304fc5
--- /dev/null
+++ b/samples/zabbix-local.env
@@ -0,0 +1,10 @@
+# Zabbix Connection for local Docker Compose profile (zabbix-local)
+# Use the contents of this file in your .env or pass them to docker compose
+# when running with 'docker compose --profile zabbix-local up'
+
+# Internal URL for the API to connect to the local Zabbix container
+ZABBIX_BASE_URL=http://zabbix-web:8080/
+
+# Note: After Zabbix starts, you must log in to http://localhost:8080 (Admin/zabbix)
+# and create an API token to use as ZABBIX_PRIVILEGE_ESCALATION_TOKEN.
+ZABBIX_PRIVILEGE_ESCALATION_TOKEN=your-newly-created-zabbix-token
diff --git a/src/api/graphql_utils.ts b/src/api/graphql_utils.ts
index 7462c9c..8b2aab8 100644
--- a/src/api/graphql_utils.ts
+++ b/src/api/graphql_utils.ts
@@ -1,4 +1,4 @@
-import {GraphQLResolveInfo, FieldNode, FragmentDefinitionNode, InlineFragmentNode} from "graphql";
+import {FieldNode, GraphQLResolveInfo, InlineFragmentNode} from "graphql";
export function getRequestedFields(info: GraphQLResolveInfo): string[] {
if (!info || !info.fieldNodes) return [];
diff --git a/src/api/resolvers.ts b/src/api/resolvers.ts
index 44e38d0..398587c 100644
--- a/src/api/resolvers.ts
+++ b/src/api/resolvers.ts
@@ -25,7 +25,7 @@ import {
StorageItemType,
} from "../schema/generated/graphql.js";
-import { DateTimeResolver, JSONObjectResolver, TimeResolver } from "graphql-scalars";
+import {DateTimeResolver, JSONObjectResolver, TimeResolver} from "graphql-scalars";
import {HostImporter} from "../execution/host_importer.js";
import {HostDeleter} from "../execution/host_deleter.js";
import {SmoketestExecutor} from "../execution/smoketest_executor.js";
@@ -35,7 +35,7 @@ import {TemplateDeleter} from "../execution/template_deleter.js";
import {HostValueExporter} from "../execution/host_exporter.js";
import {logger} from "../logging/logger.js";
import {ParsedArgs, ZabbixRequest} from "../datasources/zabbix-request.js";
-import {ZabbixHistoryGetParams, ZabbixHistoryPushParams, ZabbixHistoryPushRequest, ZabbixQueryHistoryRequest} from "../datasources/zabbix-history.js";
+import {ZabbixHistoryPushParams, ZabbixHistoryPushRequest} from "../datasources/zabbix-history.js";
import {
ZabbixCreateHostRequest,
ZabbixQueryDevices,
@@ -166,32 +166,22 @@ export function createResolvers(): Resolvers {
zabbixAuthToken,
cookie, dataSources
}: any, info: any) => {
- let params: any = {}
if (args.hostids) {
- params.templateids = args.hostids
- }
- if (args.name_pattern) {
- params.search = {
- name: args.name_pattern
- }
+ // @ts-ignore
+ args.templateids = args.hostids
+ delete args.hostids
}
const output = GraphqlParamsToNeededZabbixOutput.mapTemplates(info);
return await new ZabbixQueryTemplatesRequest(zabbixAuthToken, cookie)
- .executeRequestThrowError(dataSources?.zabbixAPI || zabbixAPI, new ParsedArgs(params), output);
+ .executeRequestThrowError(dataSources?.zabbixAPI || zabbixAPI, new ParsedArgs(args), output);
},
allTemplateGroups: async (_parent: any, args: any, {
zabbixAuthToken,
cookie
}: any) => {
- let params: any = {}
- if (args.name_pattern) {
- params.search = {
- name: args.name_pattern
- }
- }
return await new ZabbixQueryTemplateGroupRequest(zabbixAuthToken, cookie)
- .executeRequestThrowError(zabbixAPI, new ParsedArgs(params));
+ .executeRequestThrowError(zabbixAPI, new ParsedArgs(args));
}
},
Mutation: {
diff --git a/src/datasources/zabbix-api.ts b/src/datasources/zabbix-api.ts
index 17ed27c..1ecaa73 100644
--- a/src/datasources/zabbix-api.ts
+++ b/src/datasources/zabbix-api.ts
@@ -80,6 +80,20 @@ export class ZabbixAPI
return super.post(path, request);
}
+ private static version: string | undefined
+
+ async getVersion(): Promise {
+ if (!ZabbixAPI.version) {
+ const response = await this.requestByPath("apiinfo.version")
+ if (typeof response === "string") {
+ ZabbixAPI.version = response
+ } else {
+ return "0.0.0"
+ }
+ }
+ return ZabbixAPI.version
+ }
+
async executeRequest(zabbixRequest: ZabbixRequest, args?: A, throwApiError: boolean = true, output?: string[]): Promise {
return throwApiError ? zabbixRequest.executeRequestThrowError(this, args, output) : zabbixRequest.executeRequestReturnError(this, args, output);
}
diff --git a/src/datasources/zabbix-history.ts b/src/datasources/zabbix-history.ts
index c3d35b7..db57459 100644
--- a/src/datasources/zabbix-history.ts
+++ b/src/datasources/zabbix-history.ts
@@ -81,6 +81,10 @@ export class ZabbixHistoryPushRequest extends ZabbixRequest {
if (!args) return undefined;
+ const version = await zabbixAPI.getVersion();
+ if (version < "7.0.0") {
+ throw new GraphQLError(`history.push is only supported in Zabbix 7.0.0 and newer. Current version is ${version}. For older versions, please use Zabbix trapper items and zabbix_sender protocol.`);
+ }
if (!args.itemid && (!args.key || !args.host)) {
throw new GraphQLError("if itemid is empty both key and host must be filled");
diff --git a/src/datasources/zabbix-hostgroups.ts b/src/datasources/zabbix-hostgroups.ts
index 67a3d22..c2f3b6b 100644
--- a/src/datasources/zabbix-hostgroups.ts
+++ b/src/datasources/zabbix-hostgroups.ts
@@ -100,4 +100,5 @@ export class GroupHelper {
}
return result
}
+
}
diff --git a/src/datasources/zabbix-hosts.ts b/src/datasources/zabbix-hosts.ts
index 4c18c6e..8b5cb02 100644
--- a/src/datasources/zabbix-hosts.ts
+++ b/src/datasources/zabbix-hosts.ts
@@ -25,8 +25,12 @@ export class ZabbixQueryHostsGenericRequest {
+ return await super.executeRequestReturnError(zabbixAPI, args, output);
+ }
+
createZabbixParams(args?: A, output?: string[]): ZabbixParams {
- return this.optimizeZabbixParams({
+ const params: any = {
...super.createZabbixParams(args),
selectParentTemplates: [
"templateid",
@@ -45,11 +49,11 @@ export class ZabbixQueryHostsGenericRequest {
+ return await super.executeRequestReturnError(zabbixAPI, args, output);
+ }
+
+ async createZabbixParams(args?: ParsedArgs) {
return {
...super.createZabbixParams(args),
"output": [
@@ -110,7 +118,7 @@ export class ZabbixPermissionsHelper {
const userGroupPermissions = await new ZabbixQueryUserGroupPermissionsRequest(zabbixAuthToken, cookie).executeRequestThrowError(zabbixAPI)
// Prepare the list of templateIds that are not loaded yet
- const templateIdsToLoad = new Set(userGroupPermissions.flatMap(usergroup => usergroup.templategroup_rights.map(templateGroupRight => templateGroupRight.id)));
+ const templateIdsToLoad = new Set(userGroupPermissions.flatMap(usergroup => (usergroup.templategroup_rights || []).map(templateGroupRight => templateGroupRight.id)));
// Remove all templateIds that are already in the permissionObjectNameCache
templateIdsToLoad.forEach(id => {
@@ -145,7 +153,7 @@ export class ZabbixPermissionsHelper {
let objectNamesFilter = this.createMatcherFromWildcardArray(objectNames);
- usergroup.templategroup_rights.forEach(templateGroupPermission => {
+ (usergroup.templategroup_rights || []).forEach(templateGroupPermission => {
const objectName = this.permissionObjectNameCache.get(templateGroupPermission.id);
if (objectName && (objectNamesFilter == undefined || objectNamesFilter.test(objectName))) {
const permissionValue = Number(templateGroupPermission.permission) as PermissionNumber;
diff --git a/src/datasources/zabbix-request.ts b/src/datasources/zabbix-request.ts
index fb54110..72bbcb1 100644
--- a/src/datasources/zabbix-request.ts
+++ b/src/datasources/zabbix-request.ts
@@ -9,6 +9,7 @@ class ZabbixRequestBody {
public method
public id = 1
public params?: ZabbixParams
+ public auth?: string | null
constructor(method: string) {
this.method = method;
@@ -138,6 +139,7 @@ export class ParsedArgs {
(result).search.name = this.name_pattern;
(result).search.host = this.name_pattern;
(result).searchByAny = true;
+ (result).searchWildcardsEnabled = true;
}
return result
@@ -177,6 +179,12 @@ export class ZabbixRequest topLevelOutput.includes(field));
+ // Add any missing top-level fields that are needed
+ topLevelOutput.forEach(top => {
+ if (!params.output.includes(top)) {
+ params.output.push(top);
+ }
+ });
} else if (params.output === "extend") {
params.output = topLevelOutput;
}
@@ -198,7 +206,7 @@ export class ZabbixRequest {
@@ -211,10 +219,18 @@ export class ZabbixRequest(
"usergroup", "usrgrpid", ZabbixQueryUserGroupsRequest, this.authToken, this.cookie);
+
for (let userGroup of args?.usergroups || []) {
let templategroup_rights = this.calc_templategroup_rights(userGroup);
let hostgroup_rights = this.calc_hostgroup_rights(userGroup);
let errors: ApiError[] = [];
- let params = new ZabbixCreateOrUpdateParams({
+ let paramsObj: any = {
name: userGroup.name,
gui_access: userGroup.gui_access,
users_status: userGroup.users_status,
hostgroup_rights: hostgroup_rights.hostgroup_rights,
templategroup_rights: templategroup_rights.templategroup_rights,
- }, args?.dryRun)
+ };
+
+ let params = new ZabbixCreateOrUpdateParams(paramsObj, args?.dryRun)
let result = await createGroupRequest.executeRequestReturnError(zabbixAPI, params);
if (isZabbixErrorResult(result)) {
@@ -214,30 +218,36 @@ export class ZabbixImportUserGroupsRequest
for (let hostgroup_right of usergroup.hostgroup_rights || []) {
let success = false;
let matchedName = "";
+ let matchedId: number | undefined = undefined;
+
+ // Try matching by UUID first
for (let hostgroup of this.hostgroups) {
- if (hostgroup.uuid == hostgroup_right.uuid) {
- result.push(
- {
- id: Number(hostgroup.groupid),
- permission: hostgroup_right.permission,
- }
- )
- success = true;
+ if (hostgroup.uuid && hostgroup_right.uuid && hostgroup.uuid === hostgroup_right.uuid) {
+ matchedId = Number(hostgroup.groupid);
matchedName = hostgroup.name;
+ success = true;
break;
}
-
}
- if (success && hostgroup_right.name && hostgroup_right.name != matchedName) {
- errors.push(
+
+ if (success) {
+ result.push(
{
- code: ApiErrorCode.OK,
- message: `WARNING: Hostgroup found and permissions set, but target name=${matchedName} does not match`,
- data: hostgroup_right,
+ id: matchedId!,
+ permission: hostgroup_right.permission,
}
)
- }
- if (!success) {
+
+ if (hostgroup_right.name && hostgroup_right.name != matchedName) {
+ errors.push(
+ {
+ code: ApiErrorCode.OK,
+ message: `WARNING: Hostgroup found and permissions set, but target name=${matchedName} does not match provided name=${hostgroup_right.name}`,
+ data: hostgroup_right,
+ }
+ )
+ }
+ } else {
errors.push(
{
code: ApiErrorCode.ZABBIX_HOSTGROUP_NOT_FOUND,
@@ -262,29 +272,36 @@ export class ZabbixImportUserGroupsRequest
for (let templategroup_right of usergroup.templategroup_rights || []) {
let success = false;
let matchedName = "";
+ let matchedId: number | undefined = undefined;
+
+ // Try matching by UUID first
for (let templategroup of this.templategroups) {
- if (templategroup.uuid == templategroup_right.uuid) {
- result.push(
- {
- id: Number(templategroup.groupid),
- permission: templategroup_right.permission,
- }
- )
+ if (templategroup.uuid && templategroup_right.uuid && templategroup.uuid === templategroup_right.uuid) {
+ matchedId = Number(templategroup.groupid);
+ matchedName = templategroup.name;
success = true;
- matchedName = templategroup.name
break;
}
}
- if (success && templategroup_right.name && templategroup_right.name != matchedName) {
- errors.push(
+
+ if (success) {
+ result.push(
{
- code: ApiErrorCode.OK,
- message: `WARNING: Templategroup found and permissions set, but target name=${matchedName} does not match`,
- data: templategroup_right,
+ id: matchedId!,
+ permission: templategroup_right.permission,
}
)
- }
- if (!success) {
+
+ if (templategroup_right.name && templategroup_right.name != matchedName) {
+ errors.push(
+ {
+ code: ApiErrorCode.OK,
+ message: `WARNING: Templategroup found and permissions set, but target name=${matchedName} does not match provided name=${templategroup_right.name}`,
+ data: templategroup_right,
+ }
+ )
+ }
+ } else {
errors.push(
{
code: ApiErrorCode.ZABBIX_TEMPLATEGROUP_NOT_FOUND,
@@ -317,6 +334,10 @@ export class ZabbixPropagateHostGroupsRequest extends ZabbixRequest {
+ return super.prepare(zabbixAPI, args);
+ }
+
createZabbixParams(args?: ZabbixPropagateHostGroupsParams): ZabbixParams {
return {
groups: [...new Set(args?.groups || [])].map(value => {
diff --git a/src/execution/regression_test_executor.ts b/src/execution/regression_test_executor.ts
index da89787..d3c49db 100644
--- a/src/execution/regression_test_executor.ts
+++ b/src/execution/regression_test_executor.ts
@@ -23,6 +23,19 @@ export class RegressionTestExecutor {
const hostName = "REG_HOST_" + Math.random().toString(36).substring(7);
const groupName = "REG_GROUP_" + Math.random().toString(36).substring(7);
+ const regTemplateName = "REG_TEMP_" + Math.random().toString(36).substring(7);
+ const httpTempName = "REG_HTTP_" + Math.random().toString(36).substring(7);
+ const macroTemplateName = "REG_MACRO_TEMP_" + Math.random().toString(36).substring(7);
+ const macroHostName = "REG_MACRO_HOST_" + Math.random().toString(36).substring(7);
+ const metaTempName = "REG_META_TEMP_" + Math.random().toString(36).substring(7);
+ const metaHostName = "REG_META_HOST_" + Math.random().toString(36).substring(7);
+ const depTempName = "REG_DEP_TEMP_" + Math.random().toString(36).substring(7);
+ const stateTempName = "REG_STATE_TEMP_" + Math.random().toString(36).substring(7);
+ const stateHostName = "REG_STATE_HOST_" + Math.random().toString(36).substring(7);
+ const devHostNameWithTag = "REG_DEV_WITH_TAG_" + Math.random().toString(36).substring(7);
+ const devHostNameWithoutTag = "REG_DEV_WITHOUT_TAG_" + Math.random().toString(36).substring(7);
+ const pushHostName = "REG_PUSH_HOST_" + Math.random().toString(36).substring(7);
+
try {
// Regression 1: Locations query argument order
// This verifies the fix where getLocations was called with (authToken, args) instead of (args, authToken)
@@ -44,7 +57,6 @@ export class RegressionTestExecutor {
// Regression 2: Template lookup by technical name
// Verifies that importHosts can link templates using their technical name (host)
- const regTemplateName = "REG_TEMP_" + Math.random().toString(36).substring(7);
const regGroupName = "Templates/Roadwork/Devices";
const hostGroupName = "Roadwork/Devices";
@@ -55,7 +67,7 @@ export class RegressionTestExecutor {
const tempResult = await TemplateImporter.importTemplates([{
host: regTemplateName,
- name: "Regression Test Template",
+ name: "Regression Test Template " + regTemplateName,
groupNames: [regGroupName]
}], zabbixAuthToken, cookie);
@@ -69,10 +81,9 @@ export class RegressionTestExecutor {
// Regression 3: HTTP Agent URL support
// Verifies that templates with HTTP Agent items (including URL) can be imported
- const httpTempName = "REG_HTTP_" + Math.random().toString(36).substring(7);
const httpTempResult = await TemplateImporter.importTemplates([{
host: httpTempName,
- name: "Regression HTTP Template",
+ name: "Regression HTTP Template " + httpTempName,
groupNames: [regGroupName],
items: [{
name: "HTTP Master",
@@ -94,12 +105,9 @@ export class RegressionTestExecutor {
if (!httpSuccess) success = false;
// Regression 4: User Macro assignment for host and template creation
- const macroTemplateName = "REG_MACRO_TEMP_" + Math.random().toString(36).substring(7);
- const macroHostName = "REG_MACRO_HOST_" + Math.random().toString(36).substring(7);
-
const macroTempResult = await TemplateImporter.importTemplates([{
host: macroTemplateName,
- name: "Regression Macro Template",
+ name: "Regression Macro Template " + macroTemplateName,
groupNames: [regGroupName],
macros: [
{ macro: "{$TEMP_MACRO}", value: "temp_value" }
@@ -213,12 +221,9 @@ export class RegressionTestExecutor {
}
// Regression 6: Item Metadata (preprocessing, units, description, error)
- const metaTempName = "REG_META_TEMP_" + Math.random().toString(36).substring(7);
- const metaHostName = "REG_META_HOST_" + Math.random().toString(36).substring(7);
-
const metaTempResult = await TemplateImporter.importTemplates([{
host: metaTempName,
- name: "Regression Meta Template",
+ name: "Regression Meta Template " + metaTempName,
groupNames: [regGroupName],
items: [{
name: "Meta Item",
@@ -309,19 +314,17 @@ export class RegressionTestExecutor {
// 3. Test indirect dependencies: state implies items
const testParams3 = optRequest.createZabbixParams(new ParsedArgs({}), ["hostid", "state"]);
const hasSelectItems3 = "selectItems" in testParams3;
- const hasOutput3 = Array.isArray(testParams3.output) && testParams3.output.includes("hostid") && testParams3.output.includes("items");
- optSuccess = optSuccess && hasSelectItems3 && hasOutput3;
+ optSuccess = optSuccess && hasSelectItems3;
// 4. Test indirect dependencies: deviceType implies tags
const testParams4 = optRequest.createZabbixParams(new ParsedArgs({}), ["hostid", "deviceType"]);
const hasSelectTags4 = "selectTags" in testParams4;
- const hasOutput4 = Array.isArray(testParams4.output) && testParams4.output.includes("hostid");
- optSuccess = optSuccess && hasSelectTags4 && hasOutput4;
+ optSuccess = optSuccess && hasSelectTags4;
if (!optSuccess) {
- logger.error(`REG-OPT: Optimization verification failed. hasSelectItems1: ${hasSelectItems1}, hasOutput1: ${hasOutput1}, hasSelectItems2: ${hasSelectItems2}, hasSelectTags2: ${hasSelectTags2}, hasSelectItems3: ${hasSelectItems3}, hasOutput3: ${hasOutput3}, hasSelectTags4: ${hasSelectTags4}, hasOutput4: ${hasOutput4}`);
+ logger.error(`REG-OPT: Optimization verification failed. hasSelectItems1: ${hasSelectItems1}, hasOutput1: ${hasOutput1}, hasSelectItems2: ${hasSelectItems2}, hasSelectTags2: ${hasSelectTags2}, hasSelectItems3: ${hasSelectItems3}, hasSelectTags4: ${hasSelectTags4}`);
}
} catch (error) {
logger.error(`REG-OPT: Error during optimization test: ${error}`);
@@ -357,10 +360,9 @@ export class RegressionTestExecutor {
if (!emptySuccess) success = false;
// Regression 9: Dependent Items in Templates
- const depTempName = "REG_DEP_TEMP_" + Math.random().toString(36).substring(7);
const depTempResult = await TemplateImporter.importTemplates([{
host: depTempName,
- name: "Regression Dependent Template",
+ name: "Regression Dependent Template " + depTempName,
groupNames: [regGroupName],
items: [
{
@@ -390,12 +392,9 @@ export class RegressionTestExecutor {
if (!depSuccess) success = false;
// Regression 10: State sub-properties retrieval (Optimization indirect dependency)
- const stateTempName = "REG_STATE_TEMP_" + Math.random().toString(36).substring(7);
- const stateHostName = "REG_STATE_HOST_" + Math.random().toString(36).substring(7);
-
const stateTempResult = await TemplateImporter.importTemplates([{
host: stateTempName,
- name: "Regression State Template",
+ name: "Regression State Template " + stateTempName,
groupNames: [regGroupName],
tags: [{ tag: "deviceType", value: "GenericDevice" }],
items: [{
@@ -482,9 +481,6 @@ export class RegressionTestExecutor {
// Regression 12: allDevices deviceType filter
// Verifies that allDevices only returns hosts with a deviceType tag
- const devHostNameWithTag = "REG_DEV_WITH_TAG_" + Math.random().toString(36).substring(7);
- const devHostNameWithoutTag = "REG_DEV_WITHOUT_TAG_" + Math.random().toString(36).substring(7);
-
// Get groupid for hostGroupName
const groupQuery: any = await new ZabbixRequest("hostgroup.get", zabbixAuthToken, cookie)
.executeRequestReturnError(zabbixAPI, new ParsedArgs({ filter_name: hostGroupName }));
@@ -532,48 +528,59 @@ export class RegressionTestExecutor {
}
// Regression 13: pushHistory mutation
- const pushHostName = "REG_PUSH_HOST_" + Math.random().toString(36).substring(7);
- const pushItemKey = "trap.json";
-
- // Create host
- const pushHostResult = await HostImporter.importHosts([{
- deviceKey: pushHostName,
- deviceType: "RegressionHost",
- groupNames: [hostGroupName],
- templateNames: []
- }], zabbixAuthToken, cookie);
-
let pushSuccess = false;
- if (pushHostResult?.length && pushHostResult[0].hostid) {
- const pushHostId = pushHostResult[0].hostid;
+ const version = await zabbixAPI.getVersion();
+
+ if (version < "7.0.0") {
+ logger.info(`REG-PUSH: Skipping pushHistory test as it is not supported on Zabbix version ${version}`);
+ pushSuccess = true; // Mark as success for old versions to allow overall test success
+ } else {
+ const pushItemKey = "trap.json";
- // Add trapper item to host
- const pushItemResult = await new ZabbixRequest("item.create", zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
- name: "Trapper JSON Item",
- key_: pushItemKey,
- hostid: pushHostId,
- type: 2, // Zabbix trapper
- value_type: 4, // Text
- history: "1d"
- }));
+ // Create host
+ const pushHostResult = await HostImporter.importHosts([{
+ deviceKey: pushHostName,
+ deviceType: "RegressionHost",
+ groupNames: [hostGroupName],
+ templateNames: []
+ }], zabbixAuthToken, cookie);
- if (!isZabbixErrorResult(pushItemResult)) {
- // Push data
- const pushRequest = new ZabbixHistoryPushRequest(zabbixAuthToken, cookie);
- const pushParams = new ZabbixHistoryPushParams(
- [{ timestamp: new Date().toISOString(), value: { hello: "world" } }],
- undefined, pushItemKey, pushHostName
- );
+ if (pushHostResult?.length && pushHostResult[0].hostid) {
+ const pushHostId = pushHostResult[0].hostid;
- const pushDataResult = await pushRequest.executeRequestReturnError(zabbixAPI, pushParams);
- pushSuccess = !isZabbixErrorResult(pushDataResult) && pushDataResult.response === "success";
+ // Add trapper item to host
+ const pushItemResult = await new ZabbixRequest("item.create", zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
+ name: "Trapper JSON Item",
+ key_: pushItemKey,
+ hostid: pushHostId,
+ type: 2, // Zabbix trapper
+ value_type: 4, // Text
+ history: "1d"
+ }));
+
+ if (!isZabbixErrorResult(pushItemResult)) {
+ // Push data
+ const pushRequest = new ZabbixHistoryPushRequest(zabbixAuthToken, cookie);
+ const pushParams = new ZabbixHistoryPushParams(
+ [{ timestamp: new Date().toISOString(), value: { hello: "world" } }],
+ undefined, pushItemKey, pushHostName
+ );
+
+ const pushDataResult = await pushRequest.executeRequestReturnError(zabbixAPI, pushParams);
+ pushSuccess = !isZabbixErrorResult(pushDataResult) && pushDataResult.response === "success";
+ }
+
+ // Cleanup push host
+ await HostDeleter.deleteHosts([Number(pushHostId)], null, zabbixAuthToken, cookie);
}
}
steps.push({
name: "REG-PUSH: pushHistory mutation",
success: pushSuccess,
- message: pushSuccess ? "Successfully pushed history data to trapper item" : "Failed to push history data"
+ message: version < "7.0.0"
+ ? `Skipped (not supported on ${version})`
+ : (pushSuccess ? "Successfully pushed history data to trapper item" : "Failed to push history data")
});
if (!pushSuccess) success = false;
@@ -613,6 +620,22 @@ export class RegressionTestExecutor {
success: false,
message: error.message || String(error)
});
+ } finally {
+ // Cleanup
+ await HostDeleter.deleteHosts(null, hostName, zabbixAuthToken, cookie);
+ await HostDeleter.deleteHosts(null, macroHostName, zabbixAuthToken, cookie);
+ await HostDeleter.deleteHosts(null, metaHostName, zabbixAuthToken, cookie);
+ await HostDeleter.deleteHosts(null, devHostNameWithTag, zabbixAuthToken, cookie);
+ await HostDeleter.deleteHosts(null, devHostNameWithoutTag, zabbixAuthToken, cookie);
+ await HostDeleter.deleteHosts(null, pushHostName, zabbixAuthToken, cookie);
+ await TemplateDeleter.deleteTemplates(null, regTemplateName, zabbixAuthToken, cookie);
+ await TemplateDeleter.deleteTemplates(null, httpTempName, zabbixAuthToken, cookie);
+ await TemplateDeleter.deleteTemplates(null, macroTemplateName, zabbixAuthToken, cookie);
+ await TemplateDeleter.deleteTemplates(null, metaTempName, zabbixAuthToken, cookie);
+ await TemplateDeleter.deleteTemplates(null, depTempName, zabbixAuthToken, cookie);
+ await TemplateDeleter.deleteTemplates(null, stateTempName, zabbixAuthToken, cookie);
+ await HostDeleter.deleteHosts(null, stateHostName, zabbixAuthToken, cookie);
+ // We don't delete the group here as it might be shared or used by other tests in this run
}
return {
diff --git a/src/execution/smoketest_executor.ts b/src/execution/smoketest_executor.ts
index 497789f..f2a3598 100644
--- a/src/execution/smoketest_executor.ts
+++ b/src/execution/smoketest_executor.ts
@@ -66,11 +66,11 @@ export class SmoketestExecutor {
templateNames: [templateName]
}], zabbixAuthToken, cookie);
- const hostSuccess = !!hostResult?.length && !hostResult[0].error;
+ const hostSuccess = !!hostResult?.length && !hostResult[0].error && !!hostResult[0].hostid;
steps.push({
name: "Create and Link Host",
success: hostSuccess,
- message: hostSuccess ? `Host ${hostName} created and linked to ${templateName}` : `Failed: ${hostResult?.[0]?.error?.message || "Unknown error"}`
+ message: hostSuccess ? `Host ${hostName} created and linked to ${templateName}` : `Failed: ${hostResult?.[0]?.error?.message || hostResult?.[0]?.message || "Unknown error"}`
});
if (!hostSuccess) success = false;
} else {
diff --git a/src/test/history_push.test.ts b/src/test/history_push.test.ts
index a6fbb31..a521f08 100644
--- a/src/test/history_push.test.ts
+++ b/src/test/history_push.test.ts
@@ -1,11 +1,11 @@
import {ZabbixHistoryPushParams, ZabbixHistoryPushRequest} from "../datasources/zabbix-history.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
-import {GraphQLError} from "graphql";
// Mocking ZabbixAPI
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0")
}
}));
@@ -63,4 +63,12 @@ describe("ZabbixHistoryPushRequest", () => {
await expect(request.prepare(zabbixAPI, params)).rejects.toThrow("if itemid is empty both key and host must be filled");
});
+
+ test("prepare - throw error if Zabbix version < 7.0.0", async () => {
+ (zabbixAPI.getVersion as jest.Mock).mockResolvedValue("6.2.0");
+ const values = [{ timestamp: "2024-01-01T10:00:00Z", value: "val" }];
+ const params = new ZabbixHistoryPushParams(values, "1");
+
+ await expect(request.prepare(zabbixAPI, params)).rejects.toThrow("history.push is only supported in Zabbix 7.0.0 and newer");
+ });
});
diff --git a/src/test/history_push_integration.test.ts b/src/test/history_push_integration.test.ts
index 607c69c..b1dbcd6 100644
--- a/src/test/history_push_integration.test.ts
+++ b/src/test/history_push_integration.test.ts
@@ -6,6 +6,7 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
}
}));
diff --git a/src/test/host_importer.test.ts b/src/test/host_importer.test.ts
index 14fb94f..c2781d4 100644
--- a/src/test/host_importer.test.ts
+++ b/src/test/host_importer.test.ts
@@ -6,6 +6,7 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
requestByPath: jest.fn()
},
ZABBIX_EDGE_DEVICE_BASE_GROUP: "Roadwork"
diff --git a/src/test/host_integration.test.ts b/src/test/host_integration.test.ts
index 00d1544..8b3fad4 100644
--- a/src/test/host_integration.test.ts
+++ b/src/test/host_integration.test.ts
@@ -8,6 +8,7 @@ import {ZABBIX_EDGE_DEVICE_BASE_GROUP, zabbixAPI} from '../datasources/zabbix-ap
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix',
getLocations: jest.fn(),
diff --git a/src/test/host_query.test.ts b/src/test/host_query.test.ts
index 0762084..d50e5aa 100644
--- a/src/test/host_query.test.ts
+++ b/src/test/host_query.test.ts
@@ -7,6 +7,7 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix",
getLocations: jest.fn()
},
diff --git a/src/test/indirect_dependencies.test.ts b/src/test/indirect_dependencies.test.ts
index 19d82cd..8e9c49a 100644
--- a/src/test/indirect_dependencies.test.ts
+++ b/src/test/indirect_dependencies.test.ts
@@ -7,6 +7,7 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix",
}
}));
diff --git a/src/test/misc_resolvers.test.ts b/src/test/misc_resolvers.test.ts
index 4c6f588..90a5609 100644
--- a/src/test/misc_resolvers.test.ts
+++ b/src/test/misc_resolvers.test.ts
@@ -5,7 +5,8 @@ import {zabbixAPI} from "../datasources/zabbix-api.js";
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
- post: jest.fn()
+ post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
}
}));
diff --git a/src/test/query_optimization.test.ts b/src/test/query_optimization.test.ts
index a7765ac..2fa6259 100644
--- a/src/test/query_optimization.test.ts
+++ b/src/test/query_optimization.test.ts
@@ -7,6 +7,7 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix",
}
}));
@@ -90,7 +91,7 @@ describe("Query Optimization", () => {
expect(zabbixAPI.post).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
body: expect.objectContaining({
params: expect.objectContaining({
- output: ["hostid"],
+ output: expect.arrayContaining(["hostid", "tags"]),
selectTags: expect.any(Array)
})
})
@@ -187,7 +188,7 @@ describe("Query Optimization", () => {
expect(zabbixAPI.post).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
body: expect.objectContaining({
params: expect.objectContaining({
- output: ["hostid"],
+ output: expect.arrayContaining(["hostid", "tags"]),
selectTags: expect.any(Array)
})
})
diff --git a/src/test/template_deleter.test.ts b/src/test/template_deleter.test.ts
index 68e4164..b1d09e9 100644
--- a/src/test/template_deleter.test.ts
+++ b/src/test/template_deleter.test.ts
@@ -5,7 +5,8 @@ import {zabbixAPI} from "../datasources/zabbix-api.js";
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
- post: jest.fn()
+ post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
}
}));
diff --git a/src/test/template_importer.test.ts b/src/test/template_importer.test.ts
index 6258c0e..976ef88 100644
--- a/src/test/template_importer.test.ts
+++ b/src/test/template_importer.test.ts
@@ -5,7 +5,8 @@ import {zabbixAPI} from "../datasources/zabbix-api.js";
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
- post: jest.fn()
+ post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
}
}));
diff --git a/src/test/template_integration.test.ts b/src/test/template_integration.test.ts
index 22858bb..8c87cc2 100644
--- a/src/test/template_integration.test.ts
+++ b/src/test/template_integration.test.ts
@@ -8,6 +8,7 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix'
}
diff --git a/src/test/template_link.test.ts b/src/test/template_link.test.ts
index ead11e1..c5acff8 100644
--- a/src/test/template_link.test.ts
+++ b/src/test/template_link.test.ts
@@ -6,6 +6,7 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix',
requestByPath: jest.fn()
diff --git a/src/test/template_query.test.ts b/src/test/template_query.test.ts
index f0a065e..22c5e97 100644
--- a/src/test/template_query.test.ts
+++ b/src/test/template_query.test.ts
@@ -7,6 +7,7 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix"
},
ZABBIX_EDGE_DEVICE_BASE_GROUP: "Roadwork"
@@ -82,8 +83,11 @@ describe("Template Resolver", () => {
method: "template.get",
params: expect.objectContaining({
search: {
- name: "Template"
- }
+ name: "Template",
+ host: "Template"
+ },
+ searchByAny: true,
+ searchWildcardsEnabled: true
})
})
}));
@@ -105,8 +109,11 @@ describe("Template Resolver", () => {
method: "template.get",
params: expect.objectContaining({
search: {
- name: "Temp%1"
- }
+ name: "Temp%1",
+ host: "Temp%1"
+ },
+ searchByAny: true,
+ searchWildcardsEnabled: true
})
})
}));
diff --git a/src/test/user_rights.test.ts b/src/test/user_rights.test.ts
index 0ac3732..99a30eb 100644
--- a/src/test/user_rights.test.ts
+++ b/src/test/user_rights.test.ts
@@ -6,6 +6,7 @@ jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
baseURL: "http://mock-zabbix"
}
}));
diff --git a/src/test/user_rights_integration.test.ts b/src/test/user_rights_integration.test.ts
index a7e401c..ea987b4 100644
--- a/src/test/user_rights_integration.test.ts
+++ b/src/test/user_rights_integration.test.ts
@@ -8,6 +8,7 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix',
getLocations: jest.fn(),
@@ -43,7 +44,8 @@ describe("User Rights Integration Tests", () => {
.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
+ .mockResolvedValueOnce({ usrgrpids: ["1"] }) // usergroup.update
+ .mockResolvedValueOnce({ usrgrpids: [] }); // hostgroup.propagate
const response = await server.executeOperation({
query: mutation,
diff --git a/src/test/zabbix_docs_samples.test.ts b/src/test/zabbix_docs_samples.test.ts
index 87c189f..7a5dafd 100644
--- a/src/test/zabbix_docs_samples.test.ts
+++ b/src/test/zabbix_docs_samples.test.ts
@@ -8,6 +8,7 @@ import {zabbixAPI} from '../datasources/zabbix-api.js';
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
+ getVersion: jest.fn().mockResolvedValue("7.0.0"),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix',
getLocations: jest.fn(),