node-red-contrib-influxdb3 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/__tests__/influxdb3.test.js +188 -2
- package/influxdb3.html +30 -1
- package/influxdb3.js +103 -5
- package/package.json +4 -4
- package/test/line-protocol-validation.test.js +83 -0
package/README.md
CHANGED
|
@@ -118,6 +118,55 @@ msg.payload = {
|
|
|
118
118
|
return msg;
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
#### 4. Array of Multiple Measurements
|
|
122
|
+
|
|
123
|
+
Send an array to write multiple measurements in a single message:
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
// Array of objects
|
|
127
|
+
msg.payload = [
|
|
128
|
+
{
|
|
129
|
+
measurement: "temperature",
|
|
130
|
+
fields: { value: 21.5 },
|
|
131
|
+
tags: { location: "room1" }
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
measurement: "temperature",
|
|
135
|
+
fields: { value: 19.8 },
|
|
136
|
+
tags: { location: "room2" }
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
measurement: "humidity",
|
|
140
|
+
fields: { value: 65 },
|
|
141
|
+
tags: { location: "room1" }
|
|
142
|
+
}
|
|
143
|
+
];
|
|
144
|
+
return msg;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
// Array of line protocol strings
|
|
149
|
+
msg.payload = [
|
|
150
|
+
"temperature,location=room1 value=21.5",
|
|
151
|
+
"temperature,location=room2 value=19.8",
|
|
152
|
+
"humidity,location=room1 value=65"
|
|
153
|
+
];
|
|
154
|
+
return msg;
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
// Mixed array (objects and strings)
|
|
159
|
+
msg.payload = [
|
|
160
|
+
"temperature,location=room1 value=21.5",
|
|
161
|
+
{
|
|
162
|
+
measurement: "humidity",
|
|
163
|
+
fields: { value: 65 },
|
|
164
|
+
tags: { location: "room1" }
|
|
165
|
+
}
|
|
166
|
+
];
|
|
167
|
+
return msg;
|
|
168
|
+
```
|
|
169
|
+
|
|
121
170
|
### Data Types
|
|
122
171
|
|
|
123
172
|
**Important:** By default, **all numbers are written as floats** to avoid schema conflicts in InfluxDB. This is because JavaScript doesn't distinguish between `1.0` and `1` (both equal `1`), which can cause issues when InfluxDB expects a float but receives an integer.
|
|
@@ -737,7 +737,20 @@ describe('buildLineProtocol – error cases', () => {
|
|
|
737
737
|
expect(done.mock.calls[0][0].message).toContain('Line protocol string is empty');
|
|
738
738
|
});
|
|
739
739
|
|
|
740
|
-
test('
|
|
740
|
+
test('empty array produces error', async () => {
|
|
741
|
+
const { writeNode } = createWriteNode();
|
|
742
|
+
const msg = {
|
|
743
|
+
payload: []
|
|
744
|
+
};
|
|
745
|
+
const send = jest.fn();
|
|
746
|
+
const done = jest.fn();
|
|
747
|
+
await writeNode._handlers.input(msg, send, done);
|
|
748
|
+
|
|
749
|
+
expect(done).toHaveBeenCalledWith(expect.any(Error));
|
|
750
|
+
expect(done.mock.calls[0][0].message).toContain('Payload array is empty');
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test('array with invalid item type produces error', async () => {
|
|
741
754
|
const { writeNode } = createWriteNode();
|
|
742
755
|
const msg = {
|
|
743
756
|
payload: [1, 2, 3]
|
|
@@ -747,7 +760,180 @@ describe('buildLineProtocol – error cases', () => {
|
|
|
747
760
|
await writeNode._handlers.input(msg, send, done);
|
|
748
761
|
|
|
749
762
|
expect(done).toHaveBeenCalledWith(expect.any(Error));
|
|
750
|
-
expect(done.mock.calls[0][0].message).toContain('
|
|
763
|
+
expect(done.mock.calls[0][0].message).toContain('Array item 0 has invalid format');
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
describe('Array payload support', () => {
|
|
768
|
+
test('writes array of line protocol strings', async () => {
|
|
769
|
+
const { writeNode, influxModule } = createWriteNode();
|
|
770
|
+
const msg = {
|
|
771
|
+
payload: [
|
|
772
|
+
'temperature,location=room1 value=21.5',
|
|
773
|
+
'temperature,location=room2 value=19.8',
|
|
774
|
+
'humidity,location=room1 value=65'
|
|
775
|
+
]
|
|
776
|
+
};
|
|
777
|
+
const send = jest.fn();
|
|
778
|
+
const done = jest.fn();
|
|
779
|
+
await writeNode._handlers.input(msg, send, done);
|
|
780
|
+
|
|
781
|
+
const client = influxModule.__getLastClientInstance();
|
|
782
|
+
expect(client.write).toHaveBeenCalledWith(
|
|
783
|
+
'temperature,location=room1 value=21.5\ntemperature,location=room2 value=19.8\nhumidity,location=room1 value=65',
|
|
784
|
+
'metrics'
|
|
785
|
+
);
|
|
786
|
+
expect(send).toHaveBeenCalledWith(msg);
|
|
787
|
+
expect(done).toHaveBeenCalled();
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
test('writes array of object payloads', async () => {
|
|
791
|
+
const { writeNode, influxModule } = createWriteNode();
|
|
792
|
+
const msg = {
|
|
793
|
+
payload: [
|
|
794
|
+
{
|
|
795
|
+
measurement: 'temperature',
|
|
796
|
+
fields: { value: 21.5 },
|
|
797
|
+
tags: { location: 'room1' }
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
measurement: 'temperature',
|
|
801
|
+
fields: { value: 19.8 },
|
|
802
|
+
tags: { location: 'room2' }
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
measurement: 'humidity',
|
|
806
|
+
fields: { value: 65 },
|
|
807
|
+
tags: { location: 'room1' }
|
|
808
|
+
}
|
|
809
|
+
]
|
|
810
|
+
};
|
|
811
|
+
const send = jest.fn();
|
|
812
|
+
const done = jest.fn();
|
|
813
|
+
await writeNode._handlers.input(msg, send, done);
|
|
814
|
+
|
|
815
|
+
const client = influxModule.__getLastClientInstance();
|
|
816
|
+
expect(client.write).toHaveBeenCalled();
|
|
817
|
+
expect(send).toHaveBeenCalledWith(msg);
|
|
818
|
+
expect(done).toHaveBeenCalled();
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test('writes mixed array of objects and line protocol strings', async () => {
|
|
822
|
+
const { writeNode, influxModule } = createWriteNode();
|
|
823
|
+
const msg = {
|
|
824
|
+
payload: [
|
|
825
|
+
'temperature,location=room1 value=21.5',
|
|
826
|
+
{
|
|
827
|
+
measurement: 'humidity',
|
|
828
|
+
fields: { value: 65 },
|
|
829
|
+
tags: { location: 'room1' }
|
|
830
|
+
},
|
|
831
|
+
'pressure,location=room1 value=1013.25'
|
|
832
|
+
]
|
|
833
|
+
};
|
|
834
|
+
const send = jest.fn();
|
|
835
|
+
const done = jest.fn();
|
|
836
|
+
await writeNode._handlers.input(msg, send, done);
|
|
837
|
+
|
|
838
|
+
const client = influxModule.__getLastClientInstance();
|
|
839
|
+
expect(client.write).toHaveBeenCalled();
|
|
840
|
+
expect(send).toHaveBeenCalledWith(msg);
|
|
841
|
+
expect(done).toHaveBeenCalled();
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
test('array objects can use msg.measurement as fallback', async () => {
|
|
845
|
+
const { writeNode, influxModule } = createWriteNode();
|
|
846
|
+
const msg = {
|
|
847
|
+
measurement: 'temperature',
|
|
848
|
+
payload: [
|
|
849
|
+
{
|
|
850
|
+
fields: { value: 21.5 },
|
|
851
|
+
tags: { location: 'room1' }
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
fields: { value: 19.8 },
|
|
855
|
+
tags: { location: 'room2' }
|
|
856
|
+
}
|
|
857
|
+
]
|
|
858
|
+
};
|
|
859
|
+
const send = jest.fn();
|
|
860
|
+
const done = jest.fn();
|
|
861
|
+
await writeNode._handlers.input(msg, send, done);
|
|
862
|
+
|
|
863
|
+
const client = influxModule.__getLastClientInstance();
|
|
864
|
+
expect(client.write).toHaveBeenCalled();
|
|
865
|
+
expect(send).toHaveBeenCalledWith(msg);
|
|
866
|
+
expect(done).toHaveBeenCalled();
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
test('array item error includes item index', async () => {
|
|
870
|
+
const { RED, influxModule } = setup();
|
|
871
|
+
const ConfigCtor = RED._types['influxdb3-config'];
|
|
872
|
+
const WriteCtor = RED._types['influxdb3-write'];
|
|
873
|
+
|
|
874
|
+
const configNode = new ConfigCtor({
|
|
875
|
+
host: 'https://example.com',
|
|
876
|
+
database: 'metrics',
|
|
877
|
+
name: 'Test',
|
|
878
|
+
credentials: { token: 'token' }
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
// Create node without default measurement
|
|
882
|
+
const writeNode = new WriteCtor({
|
|
883
|
+
influxdb: configNode,
|
|
884
|
+
measurement: '', // No default measurement
|
|
885
|
+
database: ''
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
const msg = {
|
|
889
|
+
payload: [
|
|
890
|
+
'temperature,location=room1 value=21.5',
|
|
891
|
+
{
|
|
892
|
+
// Missing measurement and no fallback
|
|
893
|
+
fields: { value: 19.8 }
|
|
894
|
+
}
|
|
895
|
+
]
|
|
896
|
+
};
|
|
897
|
+
const send = jest.fn();
|
|
898
|
+
const done = jest.fn();
|
|
899
|
+
await writeNode._handlers.input(msg, send, done);
|
|
900
|
+
|
|
901
|
+
expect(done).toHaveBeenCalledWith(expect.any(Error));
|
|
902
|
+
expect(done.mock.calls[0][0].message).toContain('Array item 1');
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
test('array with empty string produces error', async () => {
|
|
906
|
+
const { writeNode } = createWriteNode();
|
|
907
|
+
const msg = {
|
|
908
|
+
payload: [
|
|
909
|
+
'temperature,location=room1 value=21.5',
|
|
910
|
+
' ',
|
|
911
|
+
'humidity,location=room1 value=65'
|
|
912
|
+
]
|
|
913
|
+
};
|
|
914
|
+
const send = jest.fn();
|
|
915
|
+
const done = jest.fn();
|
|
916
|
+
await writeNode._handlers.input(msg, send, done);
|
|
917
|
+
|
|
918
|
+
expect(done).toHaveBeenCalledWith(expect.any(Error));
|
|
919
|
+
expect(done.mock.calls[0][0].message).toContain('Array item 1 is an empty string');
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
test('array with invalid line protocol produces error with index', async () => {
|
|
923
|
+
const { writeNode } = createWriteNode();
|
|
924
|
+
const msg = {
|
|
925
|
+
payload: [
|
|
926
|
+
'temperature,location=room1 value=21.5',
|
|
927
|
+
'invalid line protocol no equals',
|
|
928
|
+
'humidity,location=room1 value=65'
|
|
929
|
+
]
|
|
930
|
+
};
|
|
931
|
+
const send = jest.fn();
|
|
932
|
+
const done = jest.fn();
|
|
933
|
+
await writeNode._handlers.input(msg, send, done);
|
|
934
|
+
|
|
935
|
+
expect(done).toHaveBeenCalledWith(expect.any(Error));
|
|
936
|
+
expect(done.mock.calls[0][0].message).toContain('Array item 1');
|
|
751
937
|
});
|
|
752
938
|
});
|
|
753
939
|
|
package/influxdb3.html
CHANGED
|
@@ -117,11 +117,12 @@
|
|
|
117
117
|
|
|
118
118
|
<h3>Inputs</h3>
|
|
119
119
|
<dl class="message-properties">
|
|
120
|
-
<dt>payload <span class="property-type">string | object</span></dt>
|
|
120
|
+
<dt>payload <span class="property-type">string | object | array</span></dt>
|
|
121
121
|
<dd>The data to write. Can be either:
|
|
122
122
|
<ul>
|
|
123
123
|
<li>A string in line protocol format</li>
|
|
124
124
|
<li>An object with <code>fields</code> (required) and optionally <code>tags</code> and <code>timestamp</code></li>
|
|
125
|
+
<li>An array of strings (line protocol) or objects (multiple measurements in one write)</li>
|
|
125
126
|
</ul>
|
|
126
127
|
</dd>
|
|
127
128
|
<dt class="optional">measurement <span class="property-type">string</span></dt>
|
|
@@ -233,6 +234,34 @@ return msg;</pre>
|
|
|
233
234
|
<pre>msg.payload = "weather,location=garden temperature=18.5,humidity=75";
|
|
234
235
|
return msg;</pre>
|
|
235
236
|
|
|
237
|
+
<h4>Example 4: Array of multiple measurements</h4>
|
|
238
|
+
<pre>msg.payload = [
|
|
239
|
+
{
|
|
240
|
+
"measurement": "temperature",
|
|
241
|
+
"fields": { "value": 21.5 },
|
|
242
|
+
"tags": { "location": "room1" }
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"measurement": "temperature",
|
|
246
|
+
"fields": { "value": 19.8 },
|
|
247
|
+
"tags": { "location": "room2" }
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"measurement": "humidity",
|
|
251
|
+
"fields": { "value": 65 },
|
|
252
|
+
"tags": { "location": "room1" }
|
|
253
|
+
}
|
|
254
|
+
];
|
|
255
|
+
return msg;</pre>
|
|
256
|
+
|
|
257
|
+
<h4>Example 5: Array of line protocol strings</h4>
|
|
258
|
+
<pre>msg.payload = [
|
|
259
|
+
"temperature,location=room1 value=21.5",
|
|
260
|
+
"temperature,location=room2 value=19.8",
|
|
261
|
+
"humidity,location=room1 value=65"
|
|
262
|
+
];
|
|
263
|
+
return msg;</pre>
|
|
264
|
+
|
|
236
265
|
<h3>References</h3>
|
|
237
266
|
<ul>
|
|
238
267
|
<li><a href="https://github.com/InfluxCommunity/influxdb3-js">InfluxDB v3 JavaScript Client</a></li>
|
package/influxdb3.js
CHANGED
|
@@ -141,16 +141,54 @@ module.exports = function(RED) {
|
|
|
141
141
|
* Safely serialize a value for diagnostic logging.
|
|
142
142
|
* Handles circular references and large objects.
|
|
143
143
|
* @param {*} value
|
|
144
|
+
* @param {number} [maxLength=200] - Maximum length before truncation
|
|
144
145
|
* @returns {string}
|
|
145
146
|
*/
|
|
146
|
-
function safeStringify(value) {
|
|
147
|
+
function safeStringify(value, maxLength) {
|
|
148
|
+
maxLength = maxLength || 200;
|
|
147
149
|
try {
|
|
148
|
-
|
|
150
|
+
const str = JSON.stringify(value);
|
|
151
|
+
if (str && str.length > maxLength) {
|
|
152
|
+
return str.substring(0, maxLength) + '...(truncated)';
|
|
153
|
+
}
|
|
154
|
+
return str;
|
|
149
155
|
} catch (e) {
|
|
150
156
|
return `[unserializable: ${e.message}]`;
|
|
151
157
|
}
|
|
152
158
|
}
|
|
153
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Validate that a string looks like InfluxDB line protocol.
|
|
162
|
+
* Returns null if valid, or an error message string if invalid.
|
|
163
|
+
* @param {string} lp - Trimmed line protocol string
|
|
164
|
+
* @returns {string|null}
|
|
165
|
+
*/
|
|
166
|
+
function validateLineProtocol(lp) {
|
|
167
|
+
// Detect JSON-like strings (both valid JSON and JS object notation)
|
|
168
|
+
if (/^\{[\s\S]*}$/.test(lp) || /^\[[\s\S]*]$/.test(lp)) {
|
|
169
|
+
const preview = lp.length > 100 ? lp.substring(0, 100) + '...' : lp;
|
|
170
|
+
return (
|
|
171
|
+
'The payload appears to be a JSON/object string, not line protocol. ' +
|
|
172
|
+
'If you are sending JSON, ensure msg.payload is a parsed object (not a string). ' +
|
|
173
|
+
'Use a JSON parse node before this node to convert the string to an object. ' +
|
|
174
|
+
`Received string: ${preview}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Line protocol must have at least: measurement field=value
|
|
179
|
+
// i.e. at least one space and one '=' in the field set
|
|
180
|
+
if (!lp.includes(' ') || !lp.includes('=')) {
|
|
181
|
+
const preview = lp.length > 100 ? lp.substring(0, 100) + '...' : lp;
|
|
182
|
+
return (
|
|
183
|
+
'The payload string does not appear to be valid line protocol. ' +
|
|
184
|
+
'Expected format: measurement[,tag=val] field=val[,field=val] [timestamp]. ' +
|
|
185
|
+
`Received: ${preview}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
154
192
|
/**
|
|
155
193
|
* Process a field value and add it to a Point.
|
|
156
194
|
* Returns true if the field was added, false if it was skipped.
|
|
@@ -363,14 +401,71 @@ module.exports = function(RED) {
|
|
|
363
401
|
if (!lineProtocol) {
|
|
364
402
|
throw new Error('Line protocol string is empty');
|
|
365
403
|
}
|
|
366
|
-
|
|
404
|
+
|
|
405
|
+
// Validate line protocol format
|
|
406
|
+
const validationError = validateLineProtocol(lineProtocol);
|
|
407
|
+
if (validationError) {
|
|
408
|
+
throw new Error(validationError);
|
|
409
|
+
}
|
|
410
|
+
} else if (Array.isArray(msg.payload)) {
|
|
411
|
+
// Handle array of measurements
|
|
412
|
+
if (msg.payload.length === 0) {
|
|
413
|
+
throw new Error('Payload array is empty');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const lineProtocols = [];
|
|
417
|
+
for (let i = 0; i < msg.payload.length; i++) {
|
|
418
|
+
const item = msg.payload[i];
|
|
419
|
+
|
|
420
|
+
if (typeof item === 'string') {
|
|
421
|
+
// String line protocol
|
|
422
|
+
const lp = item.trim();
|
|
423
|
+
if (!lp) {
|
|
424
|
+
throw new Error(`Array item ${i} is an empty string`);
|
|
425
|
+
}
|
|
426
|
+
const validationError = validateLineProtocol(lp);
|
|
427
|
+
if (validationError) {
|
|
428
|
+
throw new Error(`Array item ${i}: ${validationError}`);
|
|
429
|
+
}
|
|
430
|
+
lineProtocols.push(lp);
|
|
431
|
+
} else if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
432
|
+
// Object payload - build line protocol
|
|
433
|
+
const tempMsg = {
|
|
434
|
+
...msg,
|
|
435
|
+
payload: item,
|
|
436
|
+
measurement: item.measurement || msg.measurement || node.measurement
|
|
437
|
+
};
|
|
438
|
+
const result = buildLineProtocol(tempMsg);
|
|
439
|
+
if (result.error) {
|
|
440
|
+
throw new Error(`Array item ${i}: ${result.error}`);
|
|
441
|
+
}
|
|
442
|
+
lineProtocols.push(result.lineProtocol);
|
|
443
|
+
} else {
|
|
444
|
+
throw new Error(
|
|
445
|
+
`Array item ${i} has invalid format. Expected string (line protocol) or object with fields. ` +
|
|
446
|
+
`Received: ${typeof item}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
lineProtocol = lineProtocols.join('\n');
|
|
452
|
+
} else if (msg.payload && typeof msg.payload === 'object') {
|
|
367
453
|
const result = buildLineProtocol(msg);
|
|
368
454
|
if (result.error) {
|
|
369
455
|
throw new Error(result.error);
|
|
370
456
|
}
|
|
371
457
|
lineProtocol = result.lineProtocol;
|
|
372
458
|
} else {
|
|
373
|
-
|
|
459
|
+
const actualType = typeof msg.payload;
|
|
460
|
+
const detail = msg.payload === null
|
|
461
|
+
? 'null'
|
|
462
|
+
: msg.payload === undefined
|
|
463
|
+
? 'undefined'
|
|
464
|
+
: `${actualType}${msg.payload && msg.payload.constructor ? ` [${msg.payload.constructor.name}]` : ''}: ${safeStringify(msg.payload)}`;
|
|
465
|
+
throw new Error(
|
|
466
|
+
`Invalid payload format. Expected string (line protocol), object with fields, or array of objects/strings. ` +
|
|
467
|
+
`Received: ${detail}`
|
|
468
|
+
);
|
|
374
469
|
}
|
|
375
470
|
|
|
376
471
|
// Write to InfluxDB
|
|
@@ -382,7 +477,10 @@ module.exports = function(RED) {
|
|
|
382
477
|
done();
|
|
383
478
|
|
|
384
479
|
} catch (error) {
|
|
385
|
-
|
|
480
|
+
const shortMsg = error.message
|
|
481
|
+
? (error.message.length > 80 ? error.message.substring(0, 80) + '...' : error.message)
|
|
482
|
+
: 'unknown error';
|
|
483
|
+
setStatus({ fill: 'red', shape: 'dot', text: shortMsg });
|
|
386
484
|
done(error);
|
|
387
485
|
}
|
|
388
486
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-influxdb3",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Node-RED nodes for InfluxDB v3 integration",
|
|
5
5
|
"main": "influxdb3.js",
|
|
6
6
|
"scripts": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"author": "Stuart B",
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"engines": {
|
|
19
|
-
"node": ">=
|
|
19
|
+
"node": ">=18.0.0"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@influxdata/influxdb3-client": "^2.
|
|
36
|
+
"@influxdata/influxdb3-client": "^2.1.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"jest": "^30.
|
|
39
|
+
"jest": "^30.3.0"
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for line protocol string validation.
|
|
3
|
+
* Mirrors the validateLineProtocol logic in influxdb3.js.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Re-implement the validation logic here to test it in isolation
|
|
7
|
+
// (the function is not exported from influxdb3.js)
|
|
8
|
+
function validateLineProtocol(lp) {
|
|
9
|
+
if (/^\{[\s\S]*}$/.test(lp) || /^\[[\s\S]*]$/.test(lp)) {
|
|
10
|
+
const preview = lp.length > 100 ? lp.substring(0, 100) + '...' : lp;
|
|
11
|
+
return (
|
|
12
|
+
'The payload appears to be a JSON/object string, not line protocol. ' +
|
|
13
|
+
'If you are sending JSON, ensure msg.payload is a parsed object (not a string). ' +
|
|
14
|
+
'Use a JSON parse node before this node to convert the string to an object. ' +
|
|
15
|
+
`Received string: ${preview}`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!lp.includes(' ') || !lp.includes('=')) {
|
|
20
|
+
const preview = lp.length > 100 ? lp.substring(0, 100) + '...' : lp;
|
|
21
|
+
return (
|
|
22
|
+
'The payload string does not appear to be valid line protocol. ' +
|
|
23
|
+
'Expected format: measurement[,tag=val] field=val[,field=val] [timestamp]. ' +
|
|
24
|
+
`Received: ${preview}`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('Line protocol string validation', () => {
|
|
32
|
+
test('valid line protocol returns null', () => {
|
|
33
|
+
expect(validateLineProtocol('weather,location=us-midwest temperature=82 1465839830100400200')).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('valid line protocol without timestamp returns null', () => {
|
|
37
|
+
expect(validateLineProtocol('weather,location=us-midwest temperature=82')).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('valid line protocol without tags returns null', () => {
|
|
41
|
+
expect(validateLineProtocol('weather temperature=82')).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('valid line protocol with multiple fields returns null', () => {
|
|
45
|
+
expect(validateLineProtocol('weather temperature=82,humidity=71')).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('detects JSON object string', () => {
|
|
49
|
+
const input = '{"fields":{"used":12.0},"tags":{"location":"office"}}';
|
|
50
|
+
const result = validateLineProtocol(input);
|
|
51
|
+
expect(result).toContain('JSON/object string');
|
|
52
|
+
expect(result).toContain('not line protocol');
|
|
53
|
+
expect(result).toContain('JSON parse node');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('detects JS object notation string (unquoted keys)', () => {
|
|
57
|
+
const input = '{fields:{used:12.0,path:root},tags:{location:office,node:grafana2}}';
|
|
58
|
+
const result = validateLineProtocol(input);
|
|
59
|
+
expect(result).toContain('JSON/object string');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('detects JSON array string', () => {
|
|
63
|
+
const input = '[{"measurement":"test","fields":{"value":1}}]';
|
|
64
|
+
const result = validateLineProtocol(input);
|
|
65
|
+
expect(result).toContain('JSON/object string');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('detects string with no space (not line protocol)', () => {
|
|
69
|
+
const result = validateLineProtocol('justameasurement');
|
|
70
|
+
expect(result).toContain('does not appear to be valid line protocol');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('detects string with no equals sign (not line protocol)', () => {
|
|
74
|
+
const result = validateLineProtocol('measurement nofields');
|
|
75
|
+
expect(result).toContain('does not appear to be valid line protocol');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('truncates long strings in error message', () => {
|
|
79
|
+
const longJson = '{' + '"a":1,'.repeat(50) + '"b":2}';
|
|
80
|
+
const result = validateLineProtocol(longJson);
|
|
81
|
+
expect(result).toContain('...');
|
|
82
|
+
});
|
|
83
|
+
});
|