zabbix-graphql-api/src/test/schema_dependent_queries.test.ts
Andreas Hilbig b646b8c606 feat: optimize Zabbix queries and enhance specialized device support
- Implement query optimization (reduced output, parameter skipping) to minimize Zabbix API traffic.

- Add indirect dependency handling: deviceType implies tags and state implies items.

- Move schema extensions to samples/extensions/ to clarify their role as samples.

- Enhance DistanceTrackerDevice with String time fields to support optional date portions.

- Ensure allDevices strictly filters by deviceType and populates the field in results.

- Refactor runAllRegressionTests mutation to use internal unique names and improve stability.

- Fix unnecessary Zabbix API calls for item preprocessing during template and host imports.

- Update documentation including cookbook recipes, test specifications, and optimization guides.

- Add extensive unit, integration, and regression tests covering all implemented changes.

- Update docker-compose.yml to mount the samples/ directory as a volume.

- Update IntelliJ .idea run configurations to reflect the new sample extension paths.
2026-02-02 13:20:06 +01:00

173 lines
8 KiB
TypeScript

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';
import {Config} from "../common_utils.js";
describe("Schema-dependent Queries Integration Tests", () => {
let server: ApolloServer;
let postSpy: jest.SpyInstance;
let originalSchemas: any;
let originalResolvers: any;
let originalApiVersion: any;
beforeAll(async () => {
originalSchemas = Config.ADDITIONAL_SCHEMAS;
originalResolvers = Config.ADDITIONAL_RESOLVERS;
originalApiVersion = Config.API_VERSION;
// We need to bypass the static readonly nature of Config for this test.
Object.defineProperty(Config, 'ADDITIONAL_SCHEMAS', {
value: "./samples/extensions/location_tracker_devices.graphql,./samples/extensions/location_tracker_commons.graphql,./samples/extensions/display_devices.graphql",
configurable: true
});
Object.defineProperty(Config, 'ADDITIONAL_RESOLVERS', {
value: "DistanceTrackerDevice,SinglePanelDevice",
configurable: true
});
Object.defineProperty(Config, 'API_VERSION', {
value: "1.2.3",
configurable: true
});
const schema = await schema_loader();
server = new ApolloServer({
schema,
});
postSpy = jest.spyOn(zabbixAPI, 'post');
});
afterAll(() => {
postSpy.mockRestore();
Object.defineProperty(Config, 'ADDITIONAL_SCHEMAS', { value: originalSchemas });
Object.defineProperty(Config, 'ADDITIONAL_RESOLVERS', { value: originalResolvers });
Object.defineProperty(Config, 'API_VERSION', { value: originalApiVersion });
});
test("TC-SCHEMA-01: DistanceTrackerDevice Comprehensive Query", async () => {
const filePath = join(process.cwd(), 'docs', 'queries', 'sample_distance_tracker_test_query.graphql');
const content = readFileSync(filePath, 'utf-8').replace(/\r\n/g, '\n');
const queryMatch = content.match(/```graphql\n([\s\S]*?)\n```/);
if (!queryMatch) {
throw new Error(`No graphql block found in sample query file`);
}
const query = queryMatch[1];
// Setup mock responses for Zabbix API
postSpy.mockImplementation((method: string) => {
if (method === 'apiinfo.version') return Promise.resolve("7.4.0");
if (method.startsWith('hostgroup.get')) {
return Promise.resolve([
{ groupid: "1", name: "Roadwork/Devices/Tracker" }
]);
}
if (method.startsWith('host.get')) {
return Promise.resolve([
{
hostid: "10001",
host: "TRACKER_01",
name: "Distance Tracker 01",
deviceType: "DistanceTrackerDevice", // Manually mapped because we mock post()
tags: [
{ tag: "deviceType", value: "DistanceTrackerDevice" }
],
items: [
{ itemid: "1", name: "Count", key_: "state.current.count", lastvalue: "5", lastclock: 1704103200, value_type: "3" },
{ itemid: "2", name: "Time From", key_: "state.current.timeFrom", lastvalue: "2024-01-01T10:00:00Z", lastclock: 1704103200, value_type: "4" },
{ itemid: "3", name: "Time Until", key_: "state.current.timeUntil", lastvalue: "2024-01-01T11:00:00Z", lastclock: 1704103200, value_type: "4" }
],
inheritedTags: []
},
{
hostid: "10003",
host: "TRACKER_02",
name: "Distance Tracker 02",
deviceType: "DistanceTrackerDevice",
tags: [
{ tag: "deviceType", value: "DistanceTrackerDevice" }
],
items: [
{ itemid: "10", name: "Count", key_: "state.current.count", lastvalue: "10", lastclock: 1704103200, value_type: "3" },
{ itemid: "11", name: "Time From", key_: "state.current.timeFrom", lastvalue: "09:58:09", lastclock: 1704103200, value_type: "4" }
],
inheritedTags: []
},
{
hostid: "10004",
host: "TRACKER_03",
name: "Distance Tracker 03",
deviceType: "DistanceTrackerDevice",
tags: [
{ tag: "deviceType", value: "DistanceTrackerDevice" }
],
items: [
{ itemid: "20", name: "Count", key_: "state.current.count", lastvalue: "0", lastclock: 1704103200, value_type: "3" },
{ itemid: "21", name: "Time From", key_: "state.current.timeFrom", lastvalue: "", lastclock: 1704103200, value_type: "4" }
],
inheritedTags: []
},
{
hostid: "10002",
host: "DISPLAY_01",
name: "LED Display 01",
deviceType: "SinglePanelDevice", // Manually mapped because we mock post()
tags: [
{ tag: "deviceType", value: "SinglePanelDevice" }
],
items: [
{ itemid: "4", name: "Content", key_: "state.current.values.1.contentText", lastvalue: "Roadwork Ahead", lastclock: 1704103200, value_type: "4" }
],
inheritedTags: []
}
]);
}
return Promise.resolve([]);
});
const response = await server.executeOperation({
query: query,
}, {
contextValue: { zabbixAuthToken: 'test-token', dataSources: { zabbixAPI: zabbixAPI } }
});
if (response.body.kind === 'single') {
const result = response.body.singleResult;
if (result.errors) {
console.error(`Errors in query:`, JSON.stringify(result.errors, null, 2));
}
expect(result.errors).toBeUndefined();
const data = result.data as any;
expect(data.apiVersion).toBe("1.2.3");
expect(data.zabbixVersion).toBe("7.4.0");
expect(data.allHostGroups).toHaveLength(1);
expect(data.allDevices).toBeDefined();
// Verify DistanceTrackerDevice resolution
const tracker = data.allDevices.find((d: any) => d.host === "TRACKER_01");
expect(tracker.deviceType).toBe("DistanceTrackerDevice");
expect(tracker.state.current.count).toBe(5);
expect(tracker.state.current.timeFrom).toBe("2024-01-01T10:00:00Z");
const tracker02 = data.allDevices.find((d: any) => d.host === "TRACKER_02");
expect(tracker02.state.current.count).toBe(10);
expect(tracker02.state.current.timeFrom).toBe("09:58:09");
const tracker03 = data.allDevices.find((d: any) => d.host === "TRACKER_03");
expect(tracker03.state.current.timeFrom).toBe("");
// Verify allHosts with fragments
const trackerInHosts = data.allHosts.find((h: any) => h.host === "TRACKER_01");
expect(trackerInHosts.state.current.count).toBe(5);
const displayInHosts = data.allHosts.find((h: any) => h.host === "DISPLAY_01");
expect(displayInHosts.deviceType).toBe("SinglePanelDevice");
} else {
throw new Error(`Unexpected response kind: ${response.body.kind}`);
}
});
});