- 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.
173 lines
8 KiB
TypeScript
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}`);
|
|
}
|
|
});
|
|
});
|