@cryptotaxi247 / netdata-1 / commits / 3a20750bc

Even more tests. Also improved code quality with a refactoring for better testing.

BrainDoctor committed Jul 20, 2017 at 14:58 UTC 3a20750bcad67672801bddffc4614fac59ba3ed0
5 files changed +611 -190
node.d/fronius.node.js
+162 -108
@@ -1,15 +1,15 @@
1 -'use strict';
1 +"use strict";
2
3 // This program will connect to one or more Fronius Symo Inverters.
4 // to get the Solar Power Generated (current, today).
5
6 // example configuration in netdata/conf.d/node.d/fronius.conf.md
7
8 -var url = require('url');
9 -var http = require('http');
10 -var netdata = require('netdata');
8 +var url = require("url");
9 +var http = require("http");
10 +var netdata = require("netdata");
11
12 -netdata.debug('loaded ' + __filename + ' plugin');
12 +netdata.debug("loaded " + __filename + " plugin");
13
14 var fronius = {
15 name: "Fronius",
@@ -39,7 +39,8 @@ var fronius = {
39 },
40
41 // Gets the site power chart. Will be created if not existing.
42 - getSitePowerChart: function (service, id) {
42 + getSitePowerChart: function (service, suffix) {
43 + var id = this.getChartId(service, suffix);
44 var chart = fronius.charts[id];
45 if (fronius.isDefined(chart)) return chart;
46
@@ -50,13 +51,13 @@ var fronius = {
51
52 chart = {
53 id: id, // the unique id of the chart
53 - name: '', // the unique name of the chart
54 - title: service.name + ' Current Site Power', // the title of the chart
55 - units: 'W', // the units of the chart dimensions
56 - family: 'power', // the family of the chart
57 - context: 'fronius.power', // the context of the chart
54 + name: "", // the unique name of the chart
55 + title: service.name + " Current Site Power", // the title of the chart
56 + units: "W", // the units of the chart dimensions
57 + family: "power", // the family of the chart
58 + context: "fronius.power", // the context of the chart
59 type: netdata.chartTypes.area, // the type of the chart
59 - priority: fronius.base_priority + 1, // the priority relative to others in the same family
60 + priority: fronius.base_priority + 1, // the priority relative to others in the same family
61 update_every: service.update_every, // the expected update frequency of the chart
62 dimensions: dim
63 };
@@ -67,7 +68,8 @@ var fronius = {
68 },
69
70 // Gets the site consumption chart. Will be created if not existing.
70 - getSiteConsumptionChart: function (service, id) {
71 + getSiteConsumptionChart: function (service, suffix) {
72 + var id = this.getChartId(service, suffix);
73 var chart = fronius.charts[id];
74 if (fronius.isDefined(chart)) return chart;
75 var dim = {};
@@ -75,11 +77,11 @@ var fronius = {
77
78 chart = {
79 id: id, // the unique id of the chart
78 - name: '', // the unique name of the chart
79 - title: service.name + ' Current Load', // the title of the chart
80 - units: 'W', // the units of the chart dimensions
81 - family: 'consumption', // the family of the chart
82 - context: 'fronius.consumption', // the context of the chart
80 + name: "", // the unique name of the chart
81 + title: service.name + " Current Load", // the title of the chart
82 + units: "W", // the units of the chart dimensions
83 + family: "consumption", // the family of the chart
84 + context: "fronius.consumption", // the context of the chart
85 type: netdata.chartTypes.area, // the type of the chart
86 priority: fronius.base_priority + 2, // the priority relative to others in the same family
87 update_every: service.update_every, // the expected update frequency of the chart
@@ -92,7 +94,8 @@ var fronius = {
94 },
95
96 // Gets the site consumption chart. Will be created if not existing.
95 - getSiteAutonomyChart: function (service, id) {
97 + getSiteAutonomyChart: function (service, suffix) {
98 + var id = this.getChartId(service, suffix);
99 var chart = fronius.charts[id];
100 if (fronius.isDefined(chart)) return chart;
101 var dim = {};
@@ -101,11 +104,11 @@ var fronius = {
104
105 chart = {
106 id: id, // the unique id of the chart
104 - name: '', // the unique name of the chart
105 - title: service.name + ' Current Autonomy', // the title of the chart
106 - units: '%', // the units of the chart dimensions
107 - family: 'autonomy', // the family of the chart
108 - context: 'fronius.autonomy', // the context of the chart
107 + name: "", // the unique name of the chart
108 + title: service.name + " Current Autonomy", // the title of the chart
109 + units: "%", // the units of the chart dimensions
110 + family: "autonomy", // the family of the chart
111 + context: "fronius.autonomy", // the context of the chart
112 type: netdata.chartTypes.area, // the type of the chart
113 priority: fronius.base_priority + 3, // the priority relative to others in the same family
114 update_every: service.update_every, // the expected update frequency of the chart
@@ -118,21 +121,22 @@ var fronius = {
121 },
122
123 // Gets the site energy chart for today. Will be created if not existing.
121 - getSiteEnergyTodayChart: function (service, chartId) {
124 + getSiteEnergyTodayChart: function (service, suffix) {
125 + var chartId = this.getChartId(service, suffix);
126 var chart = fronius.charts[chartId];
127 if (fronius.isDefined(chart)) return chart;
128 var dim = {};
129 dim[fronius.energyTodayId] = this.createBasicDimension(fronius.energyTodayId, "Today", 1000);
130 chart = {
131 id: chartId, // the unique id of the chart
128 - name: '', // the unique name of the chart
129 - title: service.name + ' Energy production for today', // the title of the chart
130 - units: 'kWh', // the units of the chart dimensions
131 - family: 'energy', // the family of the chart
132 - context: 'fronius.energy.today', // the context of the chart
133 - type: netdata.chartTypes.area, // the type of the chart
134 - priority: fronius.base_priority + 4, // the priority relative to others in the same family
135 - update_every: service.update_every, // the expected update frequency of the chart
132 + name: "", // the unique name of the chart
133 + title: service.name + " Energy production for today",// the title of the chart
134 + units: "kWh", // the units of the chart dimensions
135 + family: "energy", // the family of the chart
136 + context: "fronius.energy.today", // the context of the chart
137 + type: netdata.chartTypes.area, // the type of the chart
138 + priority: fronius.base_priority + 4, // the priority relative to others in the same family
139 + update_every: service.update_every, // the expected update frequency of the chart
140 dimensions: dim
141 };
142 chart = service.chart(chartId, chart);
@@ -142,21 +146,22 @@ var fronius = {
146 },
147
148 // Gets the site energy chart for today. Will be created if not existing.
145 - getSiteEnergyYearChart: function (service, chartId) {
149 + getSiteEnergyYearChart: function (service, suffix) {
150 + var chartId = this.getChartId(service, suffix);
151 var chart = fronius.charts[chartId];
152 if (fronius.isDefined(chart)) return chart;
153 var dim = {};
154 dim[fronius.energyYearId] = this.createBasicDimension(fronius.energyYearId, "Year", 1000);
155 chart = {
151 - id: chartId, // the unique id of the chart
152 - name: '', // the unique name of the chart
153 - title: service.name + ' Energy production for this year', // the title of the chart
154 - units: 'kWh', // the units of the chart dimensions
155 - family: 'energy', // the family of the chart
156 - context: 'fronius.energy.year', // the context of the chart
157 - type: netdata.chartTypes.area, // the type of the chart
158 - priority: fronius.base_priority + 5, // the priority relative to others in the same family
159 - update_every: service.update_every, // the expected update frequency of the chart
156 + id: chartId, // the unique id of the chart
157 + name: "", // the unique name of the chart
158 + title: service.name + " Energy production for this year",// the title of the chart
159 + units: "kWh", // the units of the chart dimensions
160 + family: "energy", // the family of the chart
161 + context: "fronius.energy.year", // the context of the chart
162 + type: netdata.chartTypes.area, // the type of the chart
163 + priority: fronius.base_priority + 5, // the priority relative to others in the same family
164 + update_every: service.update_every, // the expected update frequency of the chart
165 dimensions: dim
166 };
167 chart = service.chart(chartId, chart);
@@ -167,35 +172,28 @@ var fronius = {
172
173 // Gets the inverter power chart. Will be created if not existing.
174 // Needs the array of inverters in order to create a chart with all inverters as dimensions
170 - getInverterPowerChart: function (service, chartId, inverters) {
171 -
175 + getInverterPowerChart: function (service, suffix, inverters) {
176 + var chartId = this.getChartId(service, suffix);
177 var chart = fronius.charts[chartId];
178 if (fronius.isDefined(chart)) return chart;
179
180 var dim = {};
176 -
177 - var inverterCount = Object.keys(inverters).length;
178 - var inverter = inverters[inverterCount.toString()];
179 - var i = 1;
180 - for (i; i <= inverterCount; i++) {
181 - if (fronius.isUndefined(inverter)) {
182 - netdata.error("Expected an Inverter with a numerical name! " +
183 - "Have a look at your JSON output to verify.");
184 - continue;
185 - }
186 - dim[i.toString()] = this.createBasicDimension("inverter_" + i, "Inverter " + i, 1);
181 + for (var key in inverters) {
182 + var name = key;
183 + if (!isNaN(key)) name = "Inverter " + key;
184 + dim[key] = this.createBasicDimension("inverter_" + key, name, 1);
185 }
186
187 chart = {
190 - id: chartId, // the unique id of the chart
191 - name: '', // the unique name of the chart
192 - title: service.name + ' Current Inverter Output', // the title of the chart
193 - units: 'W', // the units of the chart dimensions
194 - family: 'inverters', // the family of the chart
195 - context: 'fronius.inverter.output', // the context of the chart
196 - type: netdata.chartTypes.stacked, // the type of the chart
188 + id: chartId, // the unique id of the chart
189 + name: "", // the unique name of the chart
190 + title: service.name + " Current Inverter Output",// the title of the chart
191 + units: "W", // the units of the chart dimensions
192 + family: "inverters", // the family of the chart
193 + context: "fronius.inverter.output", // the context of the chart
194 + type: netdata.chartTypes.stacked, // the type of the chart
195 priority: fronius.base_priority + 6, // the priority relative to others in the same family
198 - update_every: service.update_every, // the expected update frequency of the chart
196 + update_every: service.update_every, // the expected update frequency of the chart
197 dimensions: dim
198 };
199 chart = service.chart(chartId, chart);
@@ -205,59 +203,115 @@ var fronius = {
203 },
204
205 processResponse: function (service, content) {
208 - var json = fronius.parseResponse(content);
206 + var json = fronius.convertToJson(content);
207 if (json === null) return;
208
209 // add the service
210 service.commit();
211
212 + var chartDefinitions = fronius.parseCharts(service, json);
213 + var chartCount = chartDefinitions.length;
214 + while (chartCount--) {
215 + var chartObj = chartDefinitions[chartCount];
216 + service.begin(chartObj.chart);
217 + var dimCount = chartObj.dimensions.length;
218 + while (dimCount--) {
219 + var dim = chartObj.dimensions[dimCount];
220 + service.set(dim.name, dim.value);
221 + }
222 + service.end();
223 + }
224 + },
225 +
226 + parseCharts: function (service, json) {
227 var site = json.Body.Data.Site;
228 + return [
229 + this.parsePowerChart(service, site),
230 + this.parseConsumptionChart(service, site),
231 + this.parseAutonomyChart(service, site),
232 + this.parseEnergyTodayChart(service, site),
233 + this.parseEnergyYearChart(service, site),
234 + this.parseInverterChart(service, json.Body.Data.Inverters)
235 + ];
236 + },
237 +
238 + parsePowerChart: function (service, site) {
239 + return this.getChart(this.getSitePowerChart(service, "power"),
240 + [
241 + this.getDimension(this.powerGridId, Math.round(site.P_Grid)),
242 + this.getDimension(this.powerPvId, Math.round(Math.max(site.P_PV, 0))),
243 + this.getDimension(this.powerAccuId, Math.round(site.P_Akku))
244 + ]
245 + );
246 + },
247 +
248 + parseConsumptionChart: function (service, site) {
249 + return this.getChart(this.getSiteConsumptionChart(service, "consumption"),
250 + [this.getDimension(this.consumptionLoadId, Math.round(Math.abs(site.P_Load)))]
251 + );
252 + },
253
216 - // Site Current Power Chart
217 - service.begin(fronius.getSitePowerChart(service, 'fronius_' + service.name + '.power'));
218 - service.set(fronius.powerGridId, Math.round(site.P_Grid));
219 - service.set(fronius.powerPvId, Math.round(site.P_PV));
220 - service.set(fronius.powerAccuId, Math.round(site.P_Akku));
221 - service.end();
222 -
223 - // Site Consumption Chart
224 - service.begin(fronius.getSiteConsumptionChart(service, 'fronius_' + service.name + '.consumption'));
225 - service.set(fronius.consumptionLoadId, Math.round(Math.abs(site.P_Load)));
226 - service.end();
227 -
228 - // Site Autonomy Chart
229 - service.begin(fronius.getSiteAutonomyChart(service, 'fronius_' + service.name + '.autonomy'));
230 - service.set(fronius.autonomyId, Math.round(site.rel_Autonomy));
254 + parseAutonomyChart: function (service, site) {
255 var selfConsumption = site.rel_SelfConsumption;
232 - service.set(fronius.consumptionSelfId, Math.round(selfConsumption === null ? 100 : selfConsumption));
233 - service.end();
234 -
235 - // Site Energy Today Chart
236 - service.begin(fronius.getSiteEnergyTodayChart(service, 'fronius_' + service.name + '.energy.today'));
237 - service.set(fronius.energyTodayId, Math.round(site.E_Day));
238 - service.end();
239 -
240 - // Site Energy Year Chart
241 - service.begin(fronius.getSiteEnergyYearChart(service, 'fronius_' + service.name + '.energy.year'));
242 - service.set(fronius.energyYearId, Math.round(site.E_Year));
243 - service.end();
244 -
245 - // Inverters
246 - var inverters = json.Body.Data.Inverters;
247 - var inverterCount = Object.keys(inverters).length + 1;
248 - while (inverterCount--) {
249 - var inverter = inverters[inverterCount];
250 - if (fronius.isUndefined(inverter)) continue;
251 - service.begin(fronius.getInverterPowerChart(service, 'fronius_' + service.name + '.inverters.output', inverters));
252 - service.set(inverterCount.toString(), Math.round(inverter.P));
253 - service.end();
256 + return this.getChart(this.getSiteAutonomyChart(service, "autonomy"),
257 + [
258 + this.getDimension(this.autonomyId, Math.round(site.rel_Autonomy)),
259 + this.getDimension(this.consumptionSelfId, Math.round(selfConsumption === null ? 100 : selfConsumption))
260 + ]
261 + );
262 + },
263 +
264 + parseEnergyTodayChart: function (service, site) {
265 + return this.getChart(this.getSiteEnergyTodayChart(service, "energy.today"),
266 + [this.getDimension(this.energyTodayId, Math.round(Math.max(site.E_Day, 0)))]
267 + );
268 + },
269 +
270 + parseEnergyYearChart: function (service, site) {
271 + return this.getChart(this.getSiteEnergyYearChart(service, "energy.year"),
272 + [this.getDimension(this.energyYearId, Math.round(Math.max(site.E_Year, 0)))]
273 + );
274 + },
275 +
276 + parseInverterChart: function (service, inverters) {
277 + var dimensions = [];
278 + for (var key in inverters) {
279 + dimensions.push(this.getDimension(key, Math.round(inverters[key].P)));
280 }
281 + return this.getChart(this.getInverterPowerChart(service, "inverters.output", inverters), dimensions);
282 + },
283 +
284 + getDimension: function (name, value) {
285 + return {
286 + name: name,
287 + value: value
288 + };
289 + },
290 +
291 + getChart: function (chart, dimensions) {
292 + return {
293 + chart: chart,
294 + dimensions: dimensions
295 + };
296 + },
297 +
298 + getChartId: function (service, suffix) {
299 + return "fronius_" + service.name + "." + suffix;
300 },
301
257 - parseResponse: function (httpBody) {
302 + convertToJson: function (httpBody) {
303 if (httpBody === null) return null;
304 var json = httpBody;
260 - if (typeof httpBody !== "object") json = JSON.parse(httpBody);
305 + // can't parse if it's already a json object,
306 + // the check enables easier testing if the httpBody is already valid JSON.
307 + if (typeof httpBody !== "object") {
308 + try {
309 + json = JSON.parse(httpBody);
310 + } catch (error) {
311 + netdata.error("fronius: Got a response, but it is not valid JSON. Ignoring. Error: " + error.message);
312 + return null;
313 + }
314 + }
315 return this.isResponseValid(json) ? json : null;
316 },
317
@@ -274,11 +328,11 @@ var fronius = {
328 // its purpose is to prepare the request and call
329 // netdata.serviceExecute()
330 serviceExecute: function (name, uri, update_every) {
277 - netdata.debug(this.name + ': ' + name + ': url: ' + uri + ', update_every: ' + update_every);
331 + netdata.debug(this.name + ": " + name + ": url: " + uri + ", update_every: " + update_every);
332
333 var service = netdata.service({
334 name: name,
281 - request: netdata.requestFromURL('http://' + uri),
335 + request: netdata.requestFromURL("http://" + uri),
336 update_every: update_every,
337 module: this
338 });
@@ -313,7 +367,7 @@ var fronius = {
367 },
368
369 isUndefined: function (value) {
316 - return typeof value === 'undefined';
370 + return typeof value === "undefined";
371 },
372
373 areUndefined: function (valueArray) {
@@ -325,7 +379,7 @@ var fronius = {
379 },
380
381 isDefined: function (value) {
328 - return typeof value !== 'undefined';
382 + return typeof value !== "undefined";
383 }
384 };
385
tests/node.d/fronius.chart.spec.js
+75 -16
@@ -5,17 +5,23 @@ var netdata = require("../../node.d/node_modules/netdata");
5 var subject = require("../../node.d/fronius.node");
6
7 var service = netdata.service({
8 - name: "fronius",
8 + name: "chart",
9 module: this
10 });
11
12 describe("fronius chart creation", function () {
13
14 + var chartPrefix = "fronius_chart.";
15 +
16 beforeAll(function () {
17 // change this to enable debug log
18 netdata.options.DEBUG = false;
19 });
20
21 + afterAll(function () {
22 + deleteProperties(subject.charts)
23 + });
24 +
25 it("should return a basic chart dimension", function () {
26 var result = subject.createBasicDimension("id", "name", 2);
27
@@ -26,10 +32,10 @@ describe("fronius chart creation", function () {
32 });
33
34 it("should return the power chart definition", function () {
29 - var id = "power";
30 - var result = subject.getSitePowerChart(service, id);
35 + var suffix = "power";
36 + var result = subject.getSitePowerChart(service, suffix);
37
32 - expect(result.id).toBe(id);
38 + expect(result.id).toBe(chartPrefix + suffix);
39 expect(result.units).toBe("W");
40 expect(result.type).toBe(netdata.chartTypes.area);
41 expect(result.family).toBe("power");
@@ -41,10 +47,10 @@ describe("fronius chart creation", function () {
47 });
48
49 it("should return the consumption chart definition", function () {
44 - var id = "Load";
45 - var result = subject.getSiteConsumptionChart(service, id);
50 + var suffix = "Load";
51 + var result = subject.getSiteConsumptionChart(service, suffix);
52
47 - expect(result.id).toBe(id);
53 + expect(result.id).toBe(chartPrefix + suffix);
54 expect(result.units).toBe("W");
55 expect(result.type).toBe(netdata.chartTypes.area);
56 expect(result.family).toBe("consumption");
@@ -54,10 +60,10 @@ describe("fronius chart creation", function () {
60 });
61
62 it("should return the autonomy chart definition", function () {
57 - var id = "Autonomy";
58 - var result = subject.getSiteAutonomyChart(service, id);
63 + var suffix = "Autonomy";
64 + var result = subject.getSiteAutonomyChart(service, suffix);
65
60 - expect(result.id).toBe(id);
66 + expect(result.id).toBe(chartPrefix + suffix);
67 expect(result.units).toBe("%");
68 expect(result.type).toBe(netdata.chartTypes.area);
69 expect(result.family).toBe("autonomy");
@@ -68,10 +74,10 @@ describe("fronius chart creation", function () {
74 });
75
76 it("should return the energy today chart definition", function () {
71 - var id = "Energy today";
72 - var result = subject.getSiteEnergyTodayChart(service, id);
77 + var suffix = "Energy today";
78 + var result = subject.getSiteEnergyTodayChart(service, suffix);
79
74 - expect(result.id).toBe(id);
80 + expect(result.id).toBe(chartPrefix + suffix);
81 expect(result.units).toBe("kWh");
82 expect(result.type).toBe(netdata.chartTypes.area);
83 expect(result.family).toBe("energy");
@@ -81,10 +87,10 @@ describe("fronius chart creation", function () {
87 });
88
89 it("should return the energy year chart definition", function () {
84 - var id = "Energy year";
85 - var result = subject.getSiteEnergyYearChart(service, id);
90 + var suffix = "Energy year";
91 + var result = subject.getSiteEnergyYearChart(service, suffix);
92
87 - expect(result.id).toBe(id);
93 + expect(result.id).toBe(chartPrefix + suffix);
94 expect(result.units).toBe("kWh");
95 expect(result.type).toBe(netdata.chartTypes.area);
96 expect(result.family).toBe("energy");
@@ -93,6 +99,59 @@ describe("fronius chart creation", function () {
99 expect(result.dimensions[subject.energyYearId].name).toBe("Year");
100 });
101
102 + it("should return the inverter chart definition with a single numerical inverter", function () {
103 + var inverters = {
104 + "1": {}
105 + };
106 + var suffix = "numerical";
107 + var result = subject.getInverterPowerChart(service, suffix, inverters);
108 +
109 + expect(result.id).toBe(chartPrefix + suffix);
110 + expect(result.units).toBe("W");
111 + expect(result.type).toBe(netdata.chartTypes.stacked);
112 + expect(result.family).toBe("inverters");
113 + expect(result.context).toBe("fronius.inverter.output");
114 + expect(Object.keys(result.dimensions).length).toBe(1);
115 + expect(result.dimensions["1"].name).toBe("Inverter 1");
116 + });
117 +
118 + it("should return the inverter chart definition with a single alphabetical inverter", function () {
119 + var key = "Cellar";
120 + var inverters = {
121 + "Cellar": {}
122 + };
123 + var suffix = "alphabetical";
124 + var result = subject.getInverterPowerChart(service, suffix, inverters);
125 +
126 + expect(result.id).toBe(chartPrefix + suffix);
127 + expect(result.units).toBe("W");
128 + expect(result.type).toBe(netdata.chartTypes.stacked);
129 + expect(result.family).toBe("inverters");
130 + expect(result.context).toBe("fronius.inverter.output");
131 + expect(Object.keys(result.dimensions).length).toBe(1);
132 + expect(result.dimensions[key].name).toBe(key);
133 + });
134 +
135 + it("should return the inverter chart definition with multiple alphanumerical inverter", function () {
136 + var alpha = "Cellar";
137 + var numerical = 1;
138 + var inverters = {
139 + "Cellar": {},
140 + "1": {}
141 + };
142 + var suffix = "alphanumerical";
143 + var result = subject.getInverterPowerChart(service, suffix, inverters);
144 +
145 + expect(result.id).toBe(chartPrefix + suffix);
146 + expect(result.units).toBe("W");
147 + expect(result.type).toBe(netdata.chartTypes.stacked);
148 + expect(result.family).toBe("inverters");
149 + expect(result.context).toBe("fronius.inverter.output");
150 + expect(Object.keys(result.dimensions).length).toBe(2);
151 + expect(result.dimensions[alpha].name).toBe(alpha);
152 + expect(result.dimensions[numerical].name).toBe("Inverter " + numerical);
153 + });
154 +
155 it("should return the same chart definition on second call for lazy loading", function () {
156 var first = subject.getSitePowerChart(service, "id");
157 var second = subject.getSitePowerChart(service, "id");
tests/node.d/fronius.parse.spec.js
+288 -50
@@ -5,63 +5,301 @@ var netdata = require("../../node.d/node_modules/netdata");
5 var subject = require("../../node.d/fronius.node");
6
7 var service = netdata.service({
8 - name: "fronius",
8 + name: "parse",
9 module: this
10 });
11
12 -describe("fronius data parsing", function () {
13 -
14 - var fakeResponse = {
15 - "Head" : {
16 - "RequestArguments" : {},
17 - "Status" : {
18 - "Code" : 0,
19 - "Reason" : "",
20 - "UserMessage" : ""
21 - },
22 - "Timestamp" : "2017-07-17T16:01:04+02:00"
23 - },
24 - "Body" : {
25 - "Data" : {
26 - "Site" : {
27 - "Mode" : "meter",
28 - "P_Grid" : -3430.729923,
29 - "P_Load" : -910.270077,
30 - "P_Akku" : null,
31 - "P_PV" : 4341,
32 - "rel_SelfConsumption" : 20.969133,
33 - "rel_Autonomy" : 100,
34 - "E_Day" : 57230,
35 - "E_Year" : 6425915.5,
36 - "E_Total" : 15388710,
37 - "Meter_Location" : "grid"
38 - },
39 - "Inverters" : {
40 - "1" : {
41 - "DT" : 123,
42 - "P" : 4341,
43 - "E_Day" : 57230,
44 - "E_Year" : 6425915.5,
45 - "E_Total" : 15388710
46 - }
47 - }
48 - }
12 +var root = {
13 + "Body": {
14 + "Data": {
15 + "Site": {},
16 + "Inverters": {}
17 }
50 - };
18 + }
19 +};
20
52 - beforeAll(function () {
53 - // change this to enable debug log
54 - netdata.options.DEBUG = false;
21 +describe("fronius parsing for power chart", function () {
22 +
23 + var site = root.Body.Data.Site;
24 +
25 + afterEach(function () {
26 + deleteProperties(site);
27 + });
28 +
29 + it("should return 3000 for P_Grid when rounded", function () {
30 + site.P_Grid = 2999.501;
31 + var result = subject.parsePowerChart(service, site).dimensions[0];
32 +
33 + expect(result.name).toBe(subject.powerGridId);
34 + expect(result.value).toBe(3000);
35 + });
36 +
37 + it("should return -3000 for P_Grid", function () {
38 + site.P_Grid = -3000;
39 + var result = subject.parsePowerChart(service, site).dimensions[0];
40 +
41 + expect(result.name).toBe(subject.powerGridId);
42 + expect(result.value).toBe(-3000);
43 + });
44 +
45 + it("should return 0 for P_Grid if it is null", function () {
46 + site.P_Grid = null;
47 + var result = subject.parsePowerChart(service, site).dimensions[0];
48 +
49 + expect(result.name).toBe(subject.powerGridId);
50 + expect(result.value).toBe(0);
51 + });
52 +
53 + it("should return 0 for P_Grid if it is zero", function () {
54 + site.P_Grid = 0;
55 + var result = subject.parsePowerChart(service, site).dimensions[0];
56 +
57 + expect(result.name).toBe(subject.powerGridId);
58 + expect(result.value).toBe(0);
59 + });
60 +
61 + it("should return -100 for P_Akku", function () {
62 + // it is unclear whether negative values are possible for p_akku (couln't test, nor any API docs found).
63 + site.P_Akku = -100;
64 + var result = subject.parsePowerChart(service, site).dimensions[2];
65 +
66 + expect(result.name).toBe(subject.powerAccuId);
67 + expect(result.value).toBe(-100);
68 + });
69 +
70 + it("should return 0 for P_Akku if it is null", function () {
71 + site.P_Akku = null;
72 + var result = subject.parsePowerChart(service, site).dimensions[2];
73 +
74 + expect(result.name).toBe(subject.powerAccuId);
75 + expect(result.value).toBe(0);
76 + });
77 +
78 + it("should return 0 for P_Akku if it is zero", function () {
79 + site.P_Akku = 0;
80 + var result = subject.parsePowerChart(service, site).dimensions[2];
81 +
82 + expect(result.name).toBe(subject.powerAccuId);
83 + expect(result.value).toBe(0);
84 + });
85 +
86 + it("should return 100 for P_PV", function () {
87 + site.P_PV = 100;
88 + var result = subject.parsePowerChart(service, site).dimensions[1];
89 +
90 + expect(result.name).toBe(subject.powerPvId);
91 + expect(result.value).toBe(100);
92 + });
93 +
94 + it("should return 0 for P_PV if it is zero", function () {
95 + site.P_PV = 0;
96 + var result = subject.parsePowerChart(service, site).dimensions[1];
97 +
98 + expect(result.name).toBe(subject.powerPvId);
99 + expect(result.value).toBe(0);
100 + });
101 +
102 + it("should return 0 for P_PV if it is null", function () {
103 + site.P_PV = null;
104 + var result = subject.parsePowerChart(service, site).dimensions[1];
105 +
106 + expect(result.name).toBe(subject.powerPvId);
107 + expect(result.value).toBe(0);
108 + });
109 +
110 + it("should return 0 for P_PV if it is negative", function () {
111 + // solar panels shouldn't consume anything, only produce.
112 + site.P_PV = -1;
113 + var result = subject.parsePowerChart(service, site).dimensions[1];
114 +
115 + expect(result.name).toBe(subject.powerPvId);
116 + expect(result.value).toBe(0);
117 + });
118 +
119 +});
120 +
121 +describe("fronius parsing for consumption", function () {
122 +
123 + var site = root.Body.Data.Site;
124 +
125 + afterEach(function () {
126 + deleteProperties(site);
127 + });
128 +
129 + it("should return 1000 for P_Load when rounded", function () {
130 + site.P_Load = 1000.499;
131 + var result = subject.parseConsumptionChart(service, site).dimensions[0];
132 +
133 + expect(result.name).toBe(subject.consumptionLoadId);
134 + expect(result.value).toBe(1000);
135 + });
136 +
137 + it("should return absolute value for P_Load when negative", function () {
138 + /*
139 + with firmware 3.7.4 it is sometimes possible that negative values are returned for P_Load,
140 + which makes absolutely no sense. There is always a device that consumes some electricity around the clock.
141 + Best we can do is to make it a positive value, since 0 also doesn't make much sense.
142 + This "workaround" seems to work, as there couldn't be any strange peaks observed during long-time testing.
143 + */
144 + site.P_Load = -50;
145 + var result = subject.parseConsumptionChart(service, site).dimensions[0];
146 +
147 + expect(result.name).toBe(subject.consumptionLoadId);
148 + expect(result.value).toBe(50);
149 + });
150 +
151 + it("should return 0 for P_Load if it is null", function () {
152 + site.P_Load = null;
153 + var result = subject.parseConsumptionChart(service, site).dimensions[0];
154 +
155 + expect(result.name).toBe(subject.consumptionLoadId);
156 + expect(result.value).toBe(0);
157 + });
158 +
159 + it("should return 0 for P_Load if it is zero", function () {
160 + site.P_Load = 0;
161 + var result = subject.parseConsumptionChart(service, site).dimensions[0];
162 +
163 + expect(result.name).toBe(subject.consumptionLoadId);
164 + expect(result.value).toBe(0);
165 + });
166 +
167 +});
168 +
169 +describe("fronius parsing for autonomy", function () {
170 +
171 + var site = root.Body.Data.Site;
172 +
173 + afterEach(function () {
174 + deleteProperties(site);
175 + });
176 +
177 + it("should return 100 for rel_Autonomy", function () {
178 + site.rel_Autonomy = 100;
179 + var result = subject.parseAutonomyChart(service, site).dimensions[0];
180 +
181 + expect(result.name).toBe(subject.autonomyId);
182 + expect(result.value).toBe(100);
183 + });
184 +
185 + it("should return 0 for rel_Autonomy if it is zero", function () {
186 + site.rel_Autonomy = 0;
187 + var result = subject.parseAutonomyChart(service, site).dimensions[0];
188 +
189 + expect(result.name).toBe(subject.autonomyId);
190 + expect(result.value).toBe(0);
191 });
192
57 - it("should return a parsed value", function () {
58 - // arrange
59 - netdata.send = jasmine.createSpy("send");
60 - // act
61 - subject.processResponse(service, fakeResponse);
62 - var result = netdata.send.calls.argsFor(0)[0];
63 - // assert
64 - expect(result).toContain("SET p_grid = -3431");
193 + it("should return 0 for rel_Autonomy if it is null", function () {
194 + site.rel_Autonomy = null;
195 + var result = subject.parseAutonomyChart(service, site).dimensions[0];
196 +
197 + expect(result.name).toBe(subject.autonomyId);
198 + expect(result.value).toBe(0);
199 });
200
201 + it("should return 20 for rel_Autonomy if it is 20", function () {
202 + site.rel_Autonomy = 20.1;
203 + var result = subject.parseAutonomyChart(service, site).dimensions[0];
204 +
205 + expect(result.name).toBe(subject.autonomyId);
206 + expect(result.value).toBe(20);
207 + });
208 +
209 + it("should return 20 for rel_SelfConsumption if it is 19.5", function () {
210 + site.rel_SelfConsumption = 19.5;
211 + var result = subject.parseAutonomyChart(service, site).dimensions[1];
212 +
213 + expect(result.name).toBe(subject.consumptionSelfId);
214 + expect(result.value).toBe(20);
215 + });
216 +
217 + it("should return 100 for rel_SelfConsumption if it is null", function () {
218 + /*
219 + During testing it could be observed that the API is delivering null if the solar panels
220 + do not produce enough energy to supply the local load. But in this case it should be 100, since all
221 + the produced energy is directly consumed.
222 + */
223 + site.rel_SelfConsumption = null;
224 + var result = subject.parseAutonomyChart(service, site).dimensions[1];
225 +
226 + expect(result.name).toBe(subject.consumptionSelfId);
227 + expect(result.value).toBe(100);
228 + });
229 +
230 + it("should return 0 for rel_SelfConsumption if it is zero", function () {
231 + site.rel_SelfConsumption = 0;
232 + var result = subject.parseAutonomyChart(service, site).dimensions[1];
233 +
234 + expect(result.name).toBe(subject.consumptionSelfId);
235 + expect(result.value).toBe(0);
236 + });
237 });
238 +
239 +describe("fronius parsing for energy", function () {
240 +
241 + var site = root.Body.Data.Site;
242 +
243 + afterEach(function () {
244 + deleteProperties(site);
245 + });
246 +
247 + it("should return 10000 for E_Day", function () {
248 + site.E_Day = 10000;
249 + var result = subject.parseEnergyTodayChart(service, site).dimensions[0];
250 +
251 + expect(result.name).toBe(subject.energyTodayId);
252 + expect(result.value).toBe(10000);
253 + });
254 +
255 + it("should return 0 for E_Day if it is negative", function () {
256 + /*
257 + The solar panels can't produce negative energy, really. It would be a fault of the API.
258 + */
259 + site.E_Day = -0.4;
260 + var result = subject.parseEnergyTodayChart(service, site).dimensions[0];
261 +
262 + expect(result.name).toBe(subject.energyTodayId);
263 + expect(result.value).toBe(0);
264 + });
265 +
266 + it("should return 100'000 for E_Year", function () {
267 + site.E_Year = 100000.4;
268 + var result = subject.parseEnergyYearChart(service, site).dimensions[0];
269 +
270 + expect(result.name).toBe(subject.energyYearId);
271 + expect(result.value).toBe(100000);
272 + });
273 +
274 + it("should return 0 for E_Year if it is negative", function () {
275 + /*
276 + A return value of 0 only makes sense in the silvester night anyway, when the counter is being reset.
277 + A negative value is a fault from the API though. It wouldn't make sense.
278 + */
279 + site.E_Year = -1;
280 + var result = subject.parseEnergyYearChart(service, site).dimensions[0];
281 +
282 + expect(result.name).toBe(subject.energyYearId);
283 + expect(result.value).toBe(0);
284 + });
285 +});
286 +
287 +describe("fronius parsing for inverters", function () {
288 +
289 + var inverters = root.Body.Data.Inverters;
290 +
291 + afterEach(function () {
292 + deleteProperties(inverters);
293 + });
294 +
295 + it("should return 1000 for P for inverter with name", function () {
296 + inverters["cellar"] = {
297 + P: 1000
298 + };
299 + var result = subject.parseInverterChart(service, inverters).dimensions[0];
300 +
301 + expect(result.name).toBe("cellar");
302 + expect(result.value).toBe(1000);
303 + });
304 +
305 +});
\ No newline at end of file
tests/node.d/fronius.process.spec.js new
+74
@@ -0,0 +1,74 @@
1 +"use strict";
2 +
3 +var netdata = require("../../node.d/node_modules/netdata");
4 +// remember: subject will be a singleton!
5 +var subject = require("../../node.d/fronius.node");
6 +
7 +var service = netdata.service({
8 + name: "process",
9 + module: this
10 +});
11 +
12 +var exampleResponse = {
13 + "Body": {
14 + "Data": {
15 + "Site": {
16 + "Mode": "meter",
17 + "P_Grid": -3430.729923,
18 + "P_Load": -910.270077,
19 + "P_Akku": null,
20 + "P_PV": 4341,
21 + "rel_SelfConsumption": 20.969133,
22 + "rel_Autonomy": 100,
23 + "E_Day": 57230,
24 + "E_Year": 6425915.5,
25 + "E_Total": 15388710,
26 + "Meter_Location": "grid"
27 + },
28 + "Inverters": {
29 + "1": {
30 + "DT": 123,
31 + "P": 4341,
32 + "E_Day": 57230,
33 + "E_Year": 6425915.5,
34 + "E_Total": 15388710
35 + }
36 + }
37 + }
38 + }
39 +};
40 +
41 +describe("fronius main processing", function () {
42 +
43 + beforeAll(function () {
44 + // change this to enable debug log
45 + netdata.options.DEBUG = false;
46 + });
47 +
48 + beforeEach(function () {
49 + deleteProperties(subject.charts);
50 + });
51 +
52 + it("should send parsed values to netdata", function () {
53 + netdata.send = jasmine.createSpy("send");
54 +
55 + subject.processResponse(service, exampleResponse);
56 +
57 + expect(netdata.send.calls.count()).toBe(6);
58 +
59 + // check if some parsed values were sent.
60 + var powerChart = netdata.send.calls.argsFor(5)[0];
61 +
62 + expect(powerChart).toContain("SET p_grid = -3431");
63 + expect(powerChart).toContain("SET p_pv = 4341");
64 +
65 + var inverterChart = netdata.send.calls.argsFor(0)[0];
66 +
67 + expect(inverterChart).toContain("SET 1 = 4341");
68 +
69 + var autonomyChart = netdata.send.calls.argsFor(3)[0];
70 + expect(autonomyChart).toContain("SET rel_selfconsumption = 21");
71 + });
72 +
73 +
74 +});
\ No newline at end of file
tests/node.d/fronius.validation.spec.js
+12 -16
@@ -5,26 +5,12 @@ var netdata = require("../../node.d/node_modules/netdata");
5 var subject = require("../../node.d/fronius.node");
6
7 var service = netdata.service({
8 - name: "fronius",
8 + name: "validation",
9 module: this
10 });
11
12 describe("fronius response validation", function () {
13
14 - // this is a faked JSON response from the server.
15 - // Used with freeformatter.com/json-escape.html to escape the json and turn it into a string.
16 - var fakeResponse = "{\r\n\t\"Head\" : {\r\n\t\t\"RequestArguments\" : {},\r\n\t\t\"Status\" " +
17 - ": {\r\n\t\t\t\"Code\" : 0,\r\n\t\t\t\"Reason\" : \"\",\r\n\t\t\t\"UserMessage\" : " +
18 - "\"\"\r\n\t\t},\r\n\t\t\"Timestamp\" : \"2017-07-17T16:01:04+02:00\"\r\n\t},\r\n\t\"Body\" : " +
19 - "{\r\n\t\t\"Data\" : {\r\n\t\t\t\"Site\" : {\r\n\t\t\t\t\"Mode\" : \"meter\",\r\n\t\t\t\t\"P_Grid\" " +
20 - ": -3430.729923,\r\n\t\t\t\t\"P_Load\" : -910.270077,\r\n\t\t\t\t\"P_Akku\" : " +
21 - "null,\r\n\t\t\t\t\"P_PV\" : 4341,\r\n\t\t\t\t\"rel_SelfConsumption\" : " +
22 - "20.969133,\r\n\t\t\t\t\"rel_Autonomy\" : 100,\r\n\t\t\t\t\"E_Day\" : 57230,\r\n\t\t\t\t\"E_Year\" " +
23 - ": 6425915.5,\r\n\t\t\t\t\"E_Total\" : 15388710,\r\n\t\t\t\t\"Meter_Location\" : " +
24 - "\"grid\"\r\n\t\t\t},\r\n\t\t\t\"Inverters\" : {\r\n\t\t\t\t\"1\" : {\r\n\t\t\t\t\t\"DT\" : " +
25 - "123,\r\n\t\t\t\t\t\"P\" : 4341,\r\n\t\t\t\t\t\"E_Day\" : 57230,\r\n\t\t\t\t\t\"E_Year\" : " +
26 - "6425915.5,\r\n\t\t\t\t\t\"E_Total\" : 15388710\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}";
27 -
14 it("should do nothing if response is null", function () {
15 netdata.send = jasmine.createSpy("send");
16
@@ -35,9 +21,19 @@ describe("fronius response validation", function () {
21 });
22
23 it("should return null if response is null", function () {
38 - var result = subject.parseResponse(null);
24 + var result = subject.convertToJson(null);
25 +
26 + expect(result).toBeNull();
27 + });
28 +
29 + it("should return null and log error if response cannot be parsed", function () {
30 + netdata.error = jasmine.createSpy("error");
31 +
32 + // trailing commas are enough to create syntax exceptions
33 + var result = subject.convertToJson("{name,}");
34
35 expect(result).toBeNull();
36 + expect(netdata.error.calls.count()).toBe(1);
37 });
38
39 it("should return true if response is valid", function () {