@tak-ps/node-tak 12.8.0 → 12.10.0

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.
@@ -1,4 +1,5 @@
1
- import test from 'tape';
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
2
3
  import { EventEmitter } from 'node:events';
3
4
  import TAK, { CoT } from '../index.js';
4
5
  import { CoTParser } from '@tak-ps/node-cot';
@@ -20,26 +21,24 @@ function createFakeSocket(opts: { write?: (...args: unknown[]) => boolean } = {}
20
21
  });
21
22
  }
22
23
 
23
- test('write - resolves when all CoTs are queued and sent', async (t) => {
24
+ test('write - resolves when all CoTs are queued and sent', async () => {
24
25
  const tak = createTAK();
25
26
  tak.client = createFakeSocket();
26
27
 
27
28
  const cots = [CoT.ping(), CoT.ping()];
28
29
  await tak.write(cots);
29
30
 
30
- t.equals(tak.queue.length, 0, 'queue empty after write + pull');
31
- t.end();
31
+ assert.equal(tak.queue.length, 0, 'queue empty after write + pull');
32
32
  });
33
33
 
34
- test('write - returns early when destroyed', async (t) => {
34
+ test('write - returns early when destroyed', async () => {
35
35
  const tak = createTAK();
36
36
  tak.destroyed = true;
37
37
  await tak.write([CoT.ping()]);
38
- t.equals(tak.queue.length, 0, 'nothing queued');
39
- t.end();
38
+ assert.equal(tak.queue.length, 0, 'nothing queued');
40
39
  });
41
40
 
42
- test('write - multiple concurrent writes', async (t) => {
41
+ test('write - multiple concurrent writes', async () => {
43
42
  const tak = createTAK();
44
43
  tak.client = createFakeSocket();
45
44
 
@@ -48,12 +47,11 @@ test('write - multiple concurrent writes', async (t) => {
48
47
  const p2 = tak.write([CoT.ping()]).then(() => order.push(2));
49
48
  await Promise.all([p1, p2]);
50
49
 
51
- t.deepEquals(order, [1, 2], 'writes resolve in order');
52
- t.equals(tak.queue.length, 0, 'queue drained');
53
- t.end();
50
+ assert.deepEqual(order, [1, 2], 'writes resolve in order');
51
+ assert.equal(tak.queue.length, 0, 'queue drained');
54
52
  });
55
53
 
56
- test('pull - batches CoTs into single socket.write call', async (t) => {
54
+ test('pull - batches CoTs into single socket.write call', async () => {
57
55
  const tak = createTAK({ socketBatchSize: 64 });
58
56
  const writeCalls: string[] = [];
59
57
  tak.client = createFakeSocket({
@@ -65,13 +63,12 @@ test('pull - batches CoTs into single socket.write call', async (t) => {
65
63
 
66
64
  await tak.write([CoT.ping(), CoT.ping()]);
67
65
 
68
- t.equals(writeCalls.length, 1, 'socket.write called once for batch');
69
- t.ok(writeCalls[0].includes('<event'), 'batch contains XML events');
70
- t.equals(tak.queue.length, 0, 'queue drained');
71
- t.end();
66
+ assert.equal(writeCalls.length, 1, 'socket.write called once for batch');
67
+ assert.ok(writeCalls[0].includes('<event'), 'batch contains XML events');
68
+ assert.equal(tak.queue.length, 0, 'queue drained');
72
69
  });
73
70
 
74
- test('pull - respects backpressure and resumes on process()', async (t) => {
71
+ test('pull - respects backpressure and resumes on process()', async () => {
75
72
  const tak = createTAK({ socketBatchSize: 1 });
76
73
  let callCount = 0;
77
74
  const socket = createFakeSocket({
@@ -89,19 +86,18 @@ test('pull - respects backpressure and resumes on process()', async (t) => {
89
86
  // Both CoTs get queued immediately, pull() sends 1 and hits backpressure
90
87
  await tak.write([CoT.ping(), CoT.ping()]);
91
88
 
92
- t.equals(callCount, 1, 'one write before backpressure');
93
- t.equals(tak.queue.length, 1, 'one item still in queue');
89
+ assert.equal(callCount, 1, 'one write before backpressure');
90
+ assert.equal(tak.queue.length, 1, 'one item still in queue');
94
91
 
95
92
  // Simulate backpressure clearing (equivalent to drain event)
96
93
  socket.writableNeedDrain = false;
97
94
  tak.process();
98
95
 
99
- t.equals(callCount, 2, 'second write after backpressure cleared');
100
- t.equals(tak.queue.length, 0, 'queue drained');
101
- t.end();
96
+ assert.equal(callCount, 2, 'second write after backpressure cleared');
97
+ assert.equal(tak.queue.length, 0, 'queue drained');
102
98
  });
103
99
 
104
- test('pull - error in socket.write triggers destroy + error event', async (t) => {
100
+ test('pull - error in socket.write triggers destroy + error event', async () => {
105
101
  const tak = createTAK();
106
102
  tak.client = createFakeSocket({
107
103
  write() { throw new Error('write failed'); },
@@ -115,13 +111,12 @@ test('pull - error in socket.write triggers destroy + error event', async (t) =>
115
111
  // write() sees destroyed=true on next check and returns
116
112
  await tak.write([CoT.ping()]);
117
113
 
118
- t.equals(tak.destroyed, true, 'destroyed after write error');
119
- t.ok(errorFired, 'error event fired');
120
- t.equals(errorMsg, 'write failed', 'error message matches');
121
- t.end();
114
+ assert.equal(tak.destroyed, true, 'destroyed after write error');
115
+ assert.ok(errorFired, 'error event fired');
116
+ assert.equal(errorMsg, 'write failed', 'error message matches');
122
117
  });
123
118
 
124
- test('pull - noop when already writing', (t) => {
119
+ test('pull - noop when already writing', () => {
125
120
  const tak = createTAK();
126
121
  tak.client = createFakeSocket();
127
122
  tak.writing = true;
@@ -131,18 +126,16 @@ test('pull - noop when already writing', (t) => {
131
126
  // pull() should return immediately since writing=true
132
127
  tak.process();
133
128
 
134
- t.equals(tak.queue.length, 1, 'queue unchanged');
135
- t.end();
129
+ assert.equal(tak.queue.length, 1, 'queue unchanged');
136
130
  });
137
131
 
138
- test('flush - resolves immediately when nothing queued', async (t) => {
132
+ test('flush - resolves immediately when nothing queued', async () => {
139
133
  const tak = createTAK();
140
134
  await tak.flush();
141
- t.pass('flush resolved immediately');
142
- t.end();
135
+ assert.ok(true, 'flush resolved immediately');
143
136
  });
144
137
 
145
- test('flush - rejects when destroyed mid-flush', async (t) => {
138
+ test('flush - rejects when destroyed mid-flush', async () => {
146
139
  const tak = createTAK();
147
140
  tak.client = createFakeSocket();
148
141
 
@@ -155,15 +148,14 @@ test('flush - rejects when destroyed mid-flush', async (t) => {
155
148
 
156
149
  try {
157
150
  await p;
158
- t.fail('should have rejected');
151
+ assert.fail('should have rejected');
159
152
  } catch (err) {
160
- t.ok(err instanceof Error, 'error is an Error');
161
- t.ok((err as Error).message.includes('destroyed'), 'error mentions destroyed');
153
+ assert.ok(err instanceof Error, 'error is an Error');
154
+ assert.ok((err as Error).message.includes('destroyed'), 'error mentions destroyed');
162
155
  }
163
- t.end();
164
156
  });
165
157
 
166
- test('flush - waits for queue to drain via process()', async (t) => {
158
+ test('flush - waits for queue to drain via process()', async () => {
167
159
  const tak = createTAK({ socketBatchSize: 1 });
168
160
  let callCount = 0;
169
161
  const socket = createFakeSocket({
@@ -179,7 +171,7 @@ test('flush - waits for queue to drain via process()', async (t) => {
179
171
  tak.client = socket;
180
172
 
181
173
  await tak.write([CoT.ping(), CoT.ping()]);
182
- t.equals(tak.queue.length, 1, 'one item in queue after backpressure');
174
+ assert.equal(tak.queue.length, 1, 'one item in queue after backpressure');
183
175
 
184
176
  // Clear backpressure and drain
185
177
  socket.writableNeedDrain = false;
@@ -187,12 +179,11 @@ test('flush - waits for queue to drain via process()', async (t) => {
187
179
  tak.process();
188
180
  await flushP;
189
181
 
190
- t.equals(tak.queue.length, 0, 'queue empty after flush');
191
- t.equals(callCount, 2, 'both items sent');
192
- t.end();
182
+ assert.equal(tak.queue.length, 0, 'queue empty after flush');
183
+ assert.equal(callCount, 2, 'both items sent');
193
184
  });
194
185
 
195
- test('write - yields when queue is full and resumes after destroy', async (t) => {
186
+ test('write - yields when queue is full and resumes after destroy', async () => {
196
187
  // writeQueueSize=4 → queue capacity = 4
197
188
  const tak = createTAK({ writeQueueSize: 4 });
198
189
 
@@ -202,17 +193,16 @@ test('write - yields when queue is full and resumes after destroy', async (t) =>
202
193
  .then(() => { writeResolved = true; });
203
194
  await new Promise(r => setImmediate(r));
204
195
 
205
- t.equals(tak.queue.length, 4, 'queue at capacity');
206
- t.equals(writeResolved, false, 'write blocked waiting for space');
196
+ assert.equal(tak.queue.length, 4, 'queue at capacity');
197
+ assert.equal(writeResolved, false, 'write blocked waiting for space');
207
198
 
208
199
  // Destroy unblocks write (destroyed check in loop)
209
200
  tak.destroy();
210
201
  await p;
211
- t.ok(writeResolved, 'write resolved after destroy');
212
- t.end();
202
+ assert.ok(writeResolved, 'write resolved after destroy');
213
203
  });
214
204
 
215
- test('write - resumes after queue drains', async (t) => {
205
+ test('write - resumes after queue drains', async () => {
216
206
  // writeQueueSize=8 → queue capacity = 8, socketBatchSize=4
217
207
  const tak = createTAK({ writeQueueSize: 8, socketBatchSize: 4 });
218
208
  let callCount = 0;
@@ -230,12 +220,11 @@ test('write - resumes after queue drains', async (t) => {
230
220
  CoT.ping(), CoT.ping(), CoT.ping(), CoT.ping(), CoT.ping(),
231
221
  ]);
232
222
 
233
- t.equals(tak.queue.length, 0, 'all drained');
234
- t.ok(callCount >= 2, 'multiple socket.write calls needed');
235
- t.end();
223
+ assert.equal(tak.queue.length, 0, 'all drained');
224
+ assert.ok(callCount >= 2, 'multiple socket.write calls needed');
236
225
  });
237
226
 
238
- test('write - caller can safely modify array after write returns', async (t) => {
227
+ test('write - caller can safely modify array after write returns', async () => {
239
228
  const tak = createTAK();
240
229
  const writeCalls: string[] = [];
241
230
  tak.client = createFakeSocket({
@@ -251,13 +240,12 @@ test('write - caller can safely modify array after write returns', async (t) =>
251
240
  // Modify the array after write — should not affect what was sent
252
241
  cots.length = 0;
253
242
 
254
- t.equals(writeCalls.length, 1, 'socket.write was called');
255
- t.ok(writeCalls[0].includes('<event'), 'CoT was sent before array was cleared');
256
- t.equals(tak.queue.length, 0, 'queue empty');
257
- t.end();
243
+ assert.equal(writeCalls.length, 1, 'socket.write was called');
244
+ assert.ok(writeCalls[0].includes('<event'), 'CoT was sent before array was cleared');
245
+ assert.equal(tak.queue.length, 0, 'queue empty');
258
246
  });
259
247
 
260
- test('write - stripFlow replaces existing flow tags in outbound XML', async (t) => {
248
+ test('write - stripFlow replaces existing flow tags in outbound XML', async () => {
261
249
  const tak = createTAK();
262
250
  const writeCalls: string[] = [];
263
251
  tak.client = createFakeSocket({
@@ -292,30 +280,27 @@ test('write - stripFlow replaces existing flow tags in outbound XML', async (t)
292
280
 
293
281
  await tak.write([cot], { stripFlow: true });
294
282
 
295
- t.equals(writeCalls.length, 1, 'socket.write was called');
296
- t.notOk(writeCalls[0].includes('TAK-Server-test='), 'server flow tag removed from outbound XML');
297
- t.ok(writeCalls[0].includes('NodeCoT-'), 'node-cot flow tag added to outbound XML');
283
+ assert.equal(writeCalls.length, 1, 'socket.write was called');
284
+ assert.ok(!writeCalls[0].includes('TAK-Server-test='), 'server flow tag removed from outbound XML');
285
+ assert.ok(writeCalls[0].includes('NodeCoT-'), 'node-cot flow tag added to outbound XML');
298
286
 
299
287
  const outbound = CoTParser.from_xml(writeCalls[0]);
300
288
  const outboundFlow = outbound.raw.event.detail?.['_flow-tags_'];
301
289
 
302
- t.ok(outboundFlow, 'outbound CoT contains flow tags');
303
- t.equal(Object.keys(outboundFlow || {}).length, 1, 'outbound CoT flow object is reset to one initial tag');
304
- t.notOk(outboundFlow?.['TAK-Server-test'], 'outbound CoT flow object clears prior TAK Server tags');
290
+ assert.ok(outboundFlow, 'outbound CoT contains flow tags');
291
+ assert.equal(Object.keys(outboundFlow || {}).length, 1, 'outbound CoT flow object is reset to one initial tag');
292
+ assert.ok(!outboundFlow?.['TAK-Server-test'], 'outbound CoT flow object clears prior TAK Server tags');
305
293
 
306
294
  const feat = await CoTParser.to_geojson(cot);
307
- t.equal(feat.properties.flow?.['TAK-Server-test'], '2026-03-08T04:48:00Z', 'original CoT remains unchanged');
308
-
309
- t.end();
295
+ assert.equal(feat.properties.flow?.['TAK-Server-test'], '2026-03-08T04:48:00Z', 'original CoT remains unchanged');
310
296
  });
311
297
 
312
- test('destroy - sets destroyed and clears ping interval', (t) => {
298
+ test('destroy - sets destroyed and clears ping interval', () => {
313
299
  const tak = createTAK();
314
300
  tak.pingInterval = setInterval(() => {}, 60000);
315
301
 
316
302
  tak.destroy();
317
303
 
318
- t.equals(tak.destroyed, true, 'destroyed is true');
319
- t.equals(tak.pingInterval, undefined, 'pingInterval cleared');
320
- t.end();
304
+ assert.equal(tak.destroyed, true, 'destroyed is true');
305
+ assert.equal(tak.pingInterval, undefined, 'pingInterval cleared');
321
306
  });
@@ -1,49 +1,46 @@
1
- import test from 'tape';
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
2
3
  import { Queue } from '../lib/utils/queue.js';
3
4
 
4
- test('Queue - push/pop FIFO order', (t) => {
5
+ test('Queue - push/pop FIFO order', () => {
5
6
  const q = new Queue<number>(4);
6
7
  q.push(1);
7
8
  q.push(2);
8
9
  q.push(3);
9
- t.equals(q.pop(), 1);
10
- t.equals(q.pop(), 2);
11
- t.equals(q.pop(), 3);
12
- t.end();
10
+ assert.equal(q.pop(), 1);
11
+ assert.equal(q.pop(), 2);
12
+ assert.equal(q.pop(), 3);
13
13
  });
14
14
 
15
- test('Queue - capacity enforced', (t) => {
15
+ test('Queue - capacity enforced', () => {
16
16
  const q = new Queue<number>(2);
17
- t.equals(q.push(1), true);
18
- t.equals(q.push(2), true);
19
- t.equals(q.isFull, true);
20
- t.equals(q.push(3), false);
21
- t.equals(q.length, 2);
22
- t.end();
17
+ assert.equal(q.push(1), true);
18
+ assert.equal(q.push(2), true);
19
+ assert.equal(q.isFull, true);
20
+ assert.equal(q.push(3), false);
21
+ assert.equal(q.length, 2);
23
22
  });
24
23
 
25
- test('Queue - pop from empty', (t) => {
24
+ test('Queue - pop from empty', () => {
26
25
  const q = new Queue<number>(2);
27
- t.equals(q.pop(), undefined);
28
- t.equals(q.length, 0);
29
- t.end();
26
+ assert.equal(q.pop(), undefined);
27
+ assert.equal(q.length, 0);
30
28
  });
31
29
 
32
- test('Queue - ring buffer wraparound', (t) => {
30
+ test('Queue - ring buffer wraparound', () => {
33
31
  const q = new Queue<number>(3);
34
32
  q.push(1);
35
33
  q.push(2);
36
34
  q.push(3);
37
35
  // Pop two — head advances past midpoint
38
- t.equals(q.pop(), 1);
39
- t.equals(q.pop(), 2);
36
+ assert.equal(q.pop(), 1);
37
+ assert.equal(q.pop(), 2);
40
38
  // Push two more — tail wraps around
41
39
  q.push(4);
42
40
  q.push(5);
43
41
  // Remaining items come out in FIFO order
44
- t.equals(q.pop(), 3);
45
- t.equals(q.pop(), 4);
46
- t.equals(q.pop(), 5);
47
- t.equals(q.length, 0);
48
- t.end();
42
+ assert.equal(q.pop(), 3);
43
+ assert.equal(q.pop(), 4);
44
+ assert.equal(q.pop(), 5);
45
+ assert.equal(q.length, 0);
49
46
  });
@@ -0,0 +1,62 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Readable } from 'node:stream';
4
+ import { Client } from 'undici';
5
+ import type { IncomingHttpHeaders } from 'node:http';
6
+ import type { Dispatcher } from 'undici';
7
+ import TAKAPI from '../lib/api.js';
8
+ import { APIAuthCertificate } from '../lib/auth.js';
9
+ import stream2buffer from '../lib/stream.js';
10
+
11
+ type RequestArgs = Parameters<Client['request']>[0];
12
+
13
+ test('Files.uploadPackage serializes multipart for certificate auth', async () => {
14
+ const originalRequest = Client.prototype.request;
15
+
16
+ let captured: RequestArgs | undefined;
17
+
18
+ Client.prototype.request = async function(opts: RequestArgs): Promise<Dispatcher.ResponseData> {
19
+ captured = opts;
20
+
21
+ return {
22
+ statusCode: 200,
23
+ headers: {
24
+ 'content-type': 'text/plain'
25
+ },
26
+ body: Readable.from([Buffer.from('ok')]),
27
+ trailers: {}
28
+ } as unknown as Dispatcher.ResponseData;
29
+ };
30
+
31
+ try {
32
+ const api = new TAKAPI(new URL('https://tak.example.com'), new APIAuthCertificate('cert', 'key'));
33
+
34
+ const res = await api.Files.uploadPackage({
35
+ name: 'mission.zip',
36
+ creatorUid: 'user-1',
37
+ hash: 'hash-123',
38
+ keywords: ['alpha'],
39
+ groups: ['Blue']
40
+ }, Buffer.from('zip-bytes'));
41
+
42
+ assert.equal(res, 'ok');
43
+ assert.ok(captured);
44
+ assert.equal(captured.path, '/Marti/sync/missionupload?filename=mission.zip&creatorUid=user-1&hash=hash-123&keyword=missionpackage&keyword=alpha&Groups=Blue');
45
+ const headers = captured.headers as IncomingHttpHeaders;
46
+ assert.equal(typeof headers['Content-Type'], 'string');
47
+ assert.equal(typeof headers['Content-Length'], 'string');
48
+ assert.match(headers['Content-Type'] as string, /^multipart\/form-data; boundary=----node-tak-/);
49
+ assert.equal(Number(headers['Content-Length']) > 0, true);
50
+
51
+ assert.ok(captured.body instanceof Readable);
52
+ const body = await stream2buffer(captured.body);
53
+ const multipart = body.toString('utf8');
54
+
55
+ assert.match(multipart, /Content-Disposition: form-data; name="assetfile"; filename="mission.zip"/);
56
+ assert.match(multipart, /Content-Type: application\/zip/);
57
+ assert.match(multipart, /zip-bytes/);
58
+ assert.match(multipart, /--$/m);
59
+ } finally {
60
+ Client.prototype.request = originalRequest;
61
+ }
62
+ });