zabbix-graphql-api/src/test/template_importer.test.ts
Andreas Hilbig fb5e9cbe81 feat: improve Zabbix multi-version compatibility and introduce local development environment
This update enhances compatibility across multiple Zabbix versions and introduces tools for easier local development and testing.

Key improvements and verified version support:
- Verified Zabbix version support: 6.2, 6.4, 7.0, and 7.4.
- Version-specific feature handling:
  - `history.push` is enabled only for Zabbix 7.0+; older versions skip it with a clear error or notice.
  - Conditional JSON-RPC authentication: the `auth` field is automatically added to the request body for versions older than 6.4.
  - Implemented static Zabbix version caching in the datasource to minimize redundant API calls.
- Query optimization refinements:
  - Added mapping for implied fields (e.g., `state` -> `items`, `deviceType` -> `tags`).
  - Automatically prune unnecessary Zabbix parameters (like `selectItems` or `selectTags`) when not requested.
- Local development environment:
  - Added a new `zabbix-local` Docker Compose profile that includes PostgreSQL, Zabbix Server, and Zabbix Web.
  - Supports testing different versions by passing the `ZABBIX_VERSION` environment variable (e.g., 6.2, 6.4, 7.0, 7.4).
  - Provided a sample environment file at `samples/zabbix-local.env`.
- Documentation and Roadmap:
  - Updated README with a comprehensive version compatibility matrix and local environment instructions.
  - Created a new guide: `docs/howtos/local_development.md`.
  - Updated maintenance guides and added "Local Development Environment" as an achieved milestone in the roadmap.
- Test suite enhancements:
  - Improved Smoketest and RegressionTest executors with more reliable resource cleanup and error reporting.
  - Made tests version-aware to prevent failures on older Zabbix instances.

BREAKING CHANGE: Dropped Zabbix 6.0 specific workarounds; the minimum supported version is now 6.2.
2026-02-04 14:01:53 +01:00

195 lines
7.9 KiB
TypeScript

import {TemplateImporter} from "../execution/template_importer.js";
import {zabbixAPI} from "../datasources/zabbix-api.js";
// Mocking ZabbixAPI.executeRequest
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
executeRequest: jest.fn(),
post: jest.fn(),
getVersion: jest.fn().mockResolvedValue("7.0.0"),
}
}));
describe("TemplateImporter", () => {
beforeEach(() => {
jest.clearAllMocks();
});
test("importTemplateGroups - create new group", async () => {
const templateGroups = [{ groupName: "New Group", uuid: "uuid1" }];
// Mocking group.get to return empty (group doesn't exist)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([]);
// Mocking group.create
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ groupids: ["101"] });
const result = await TemplateImporter.importTemplateGroups(templateGroups, "token");
expect(result).toHaveLength(1);
expect(result![0].groupid).toBe(101);
expect(result![0].groupName).toBe("New Group");
});
test("importTemplateGroups - group already exists", async () => {
const templateGroups = [{ groupName: "Existing Group" }];
// Mocking group.get to return existing group
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "102", name: "Existing Group" }]);
const result = await TemplateImporter.importTemplateGroups(templateGroups, "token");
expect(result).toHaveLength(1);
expect(result![0].groupid).toBe(102);
expect(result![0].message).toContain("already exists");
});
test("importTemplates - basic template", async () => {
const templates = [{
host: "Test Template",
groupNames: ["Group1"]
}];
// Mocking group.get for Group1
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: "Group1" }]);
// Mocking template.create
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["301"] });
const result = await TemplateImporter.importTemplates(templates, "token");
expect(result).toHaveLength(1);
expect(result![0].templateid).toBe("301");
expect(result![0].host).toBe("Test Template");
});
test("importTemplates - with items, linked templates and dependent items", async () => {
const templates = [{
host: "Complex Template",
groupNames: ["Group1"],
templates: [{ name: "Linked Template" }],
items: [
{
name: "Dependent Item",
key: "dependent.key",
type: 18,
value_type: 3,
master_item: { key: "master.key" }
},
{
name: "Master Item",
key: "master.key",
type: 0,
value_type: 3,
}
]
}];
// Mocking group.get
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: "Group1" }]);
// Mocking template.get for linked template
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "401", host: "Linked Template" }]);
// Mocking template.create
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["501"] });
// Mocking item.create for Master Item (first pass will pick Master Item because Dependent Item is missing its master)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ itemids: ["601"] });
// Mocking item.create for Dependent Item (second pass)
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ itemids: ["602"] });
const result = await TemplateImporter.importTemplates(templates, "token");
expect(result).toHaveLength(1);
expect(result![0].templateid).toBe("501");
// Check template.create params
const templateCreateCall = (zabbixAPI.post as jest.Mock).mock.calls.find(call => call[1].body.method === "template.create");
expect(templateCreateCall[1].body.params.templates).toContainEqual({ templateid: "401" });
// Check item.create calls
const itemCreateCalls = (zabbixAPI.post as jest.Mock).mock.calls.filter(call => call[1].body.method === "item.create");
expect(itemCreateCalls).toHaveLength(2);
const masterCall = itemCreateCalls.find(c => c[1].body.params.name === "Master Item");
const dependentCall = itemCreateCalls.find(c => c[1].body.params.name === "Dependent Item");
expect(masterCall[1].body.params.key_).toBe("master.key");
expect(dependentCall[1].body.params.key_).toBe("dependent.key");
expect(dependentCall[1].body.params.master_itemid).toBe("601");
});
test("importTemplates - template query", async () => {
// This tests the template.get functionality used during import
const templates = [{
host: "Template A",
groupNames: ["Group1"],
templates: [{ name: "Template B" }]
}];
// Mock Group
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "1", name: "Group1" }]);
// Mock Template B lookup
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ templateid: "2", host: "Template B" }]);
// Mock Template A creation
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["3"] });
await TemplateImporter.importTemplates(templates, "token");
// Verify that template.get was called for Template B
const templateQueryCall = (zabbixAPI.post as jest.Mock).mock.calls.find(call => call[1].body.method === "template.get");
expect(templateQueryCall).toBeDefined();
expect(templateQueryCall[1].body.params.filter.host).toContain("Template B");
});
test("importTemplates - error message includes data field", async () => {
const templates = [{
host: "Error Template",
groupNames: ["Group1"]
}];
// Mocking group.get
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: "Group1" }]);
// Mocking template.create with an error including data
const zabbixError = {
error: {
code: -32602,
message: "Invalid params.",
data: "Invalid parameter \"/1\": the parameter \"key_\" is missing."
}
};
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce(zabbixError);
const result = await TemplateImporter.importTemplates(templates, "token");
expect(result).toHaveLength(1);
expect(result![0].message).toContain("Invalid params.");
expect(result![0].message).toContain("the parameter \"key_\" is missing.");
});
test("importTemplates - with macros", async () => {
const templates = [{
host: "TemplateWithMacros",
groupNames: ["Group1"],
macros: [
{ macro: "{$LAT}", value: "52.52" },
{ macro: "{$LON}", value: "13.41" }
]
}];
// Mocking group.get
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce([{ groupid: "201", name: "Group1" }]);
// Mocking template.create
(zabbixAPI.post as jest.Mock).mockResolvedValueOnce({ templateids: ["302"] });
const result = await TemplateImporter.importTemplates(templates, "token");
expect(result).toHaveLength(1);
expect(result![0].templateid).toBe("302");
// Verify that template.create was called with macros
const templateCreateCall = (zabbixAPI.post as jest.Mock).mock.calls.find(call => call[1].body.method === "template.create");
expect(templateCreateCall[1].body.params.macros).toEqual([
{ macro: "{$LAT}", value: "52.52" },
{ macro: "{$LON}", value: "13.41" }
]);
});
});