feat: add template and template group management via GraphQL
- Implemented GraphQL endpoints for importing, querying, and deleting Zabbix templates and template groups. - Added support for full template data import, including items, preprocessing steps, tags, and linked templates. - Implemented dependent item support by deferred creation logic in the template importer. - Added ability to query templates and template groups with name pattern filtering (supporting Zabbix wildcards). - Implemented batch deletion for templates and template groups by ID or name pattern. - Improved error reporting by including detailed Zabbix API error data in GraphQL responses. - Added comprehensive unit and integration tests covering all new functionality. - Provided GraphQL sample queries and mutations in the 'docs' directory for all new endpoints.
This commit is contained in:
parent
e641f8e610
commit
a3ed4886a3
22 changed files with 2450 additions and 20 deletions
176
src/test/template_importer.test.ts
Normal file
176
src/test/template_importer.test.ts
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
|
||||
import {TemplateImporter} from "../execution/template_importer.js";
|
||||
import {zabbixAPI} from "../datasources/zabbix-api.js";
|
||||
import {
|
||||
ZabbixCreateItemRequest,
|
||||
ZabbixCreateTemplateGroupRequest,
|
||||
ZabbixCreateTemplateRequest,
|
||||
ZabbixQueryItemRequest,
|
||||
ZabbixQueryTemplateGroupRequest,
|
||||
ZabbixQueryTemplatesRequest
|
||||
} from "../datasources/zabbix-templates.js";
|
||||
import {ZabbixErrorResult} from "../datasources/zabbix-request.js";
|
||||
|
||||
// Mocking ZabbixAPI.executeRequest
|
||||
jest.mock("../datasources/zabbix-api.js", () => ({
|
||||
zabbixAPI: {
|
||||
executeRequest: jest.fn(),
|
||||
post: jest.fn()
|
||||
}
|
||||
}));
|
||||
|
||||
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.");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue