node-osc
Version:
pyOSC inspired library for sending and receiving OSC messages
499 lines (428 loc) • 13.8 kB
JavaScript
var tap = require('tap');
var _osc = require('#osc');
tap.test('osc: timetag encoding with non-number value', (t) => {
// Test the else branch in writeTimeTag that writes zeros for non-number values
const bundle = {
oscType: 'bundle',
timetag: 'immediate', // Non-number value
elements: [
{
oscType: 'message',
address: '/test',
args: []
}
]
};
const buffer = _osc.toBuffer(bundle);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
t.equal(decoded.timetag, 0, 'should decode timetag as 0 for immediate execution');
t.end();
});
tap.test('osc: timetag with immediate execution values', (t) => {
// Test readTimeTag with seconds === 0 && fraction === 1
const bundle = {
oscType: 'bundle',
timetag: null, // This will trigger the non-number path
elements: [
{
oscType: 'message',
address: '/test',
args: []
}
]
};
const buffer = _osc.toBuffer(bundle);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.timetag, 0, 'should handle immediate execution timetag');
t.end();
});
tap.test('osc: argument encoding with unknown type', (t) => {
// Test encodeArgument with unknown argument type to trigger line 122
const message = {
oscType: 'message',
address: '/test',
args: [
{
type: 'unknown',
value: 'test'
}
]
};
t.throws(() => {
_osc.toBuffer(message);
}, /Unknown argument type: unknown/, 'should throw error for unknown argument type');
t.end();
});
tap.test('osc: argument encoding with boolean false', (t) => {
// Test explicit boolean false encoding to cover lines 132-133
const message = {
oscType: 'message',
address: '/test',
args: [
{
type: 'boolean',
value: false
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.args[0].value, false, 'should encode and decode false boolean');
t.end();
});
tap.test('osc: argument encoding with unsupported object', (t) => {
// Test encodeArgument with unsupported object to trigger lines 139-142
const message = {
oscType: 'message',
address: '/test',
args: [
{ unsupported: 'object' } // Object without type/value properties
]
};
t.throws(() => {
_osc.toBuffer(message);
}, /Don't know how to encode argument/, 'should throw error for unsupported object');
t.end();
});
tap.test('osc: argument encoding with undefined value', (t) => {
// Test encodeArgument with undefined to trigger error case
const message = {
oscType: 'message',
address: '/test',
args: [undefined]
};
t.throws(() => {
_osc.toBuffer(message);
}, /Don't know how to encode argument/, 'should throw error for undefined argument');
t.end();
});
tap.test('osc: argument decoding with unknown type tag', (t) => {
// Test decodeArgument with unknown type tag to trigger line 161
// We need to manually create a buffer with an invalid type tag
const addressPart = '/test\0\0\0';
const typeTagPart = ',X\0\0'; // X is not a valid OSC type tag
const buffer = Buffer.from(addressPart + typeTagPart);
t.throws(() => {
_osc.fromBuffer(buffer);
}, /I don't understand the argument code X/, 'should throw error for unknown type tag');
t.end();
});
tap.test('osc: null argument encoding and decoding', (t) => {
// Test null argument handling (N type tag)
// Since our current implementation doesn't directly support null in encoding,
// let's test that we can at least decode it if we manually create the buffer
const addressPart = '/test\0\0\0';
const typeTagPart = ',N\0\0'; // N is null type tag
const buffer = Buffer.from(addressPart + typeTagPart);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.args[0].value, null, 'should decode null argument');
t.end();
});
tap.test('osc: double type argument encoding', (t) => {
// Test double type argument which should fall back to float
const message = {
oscType: 'message',
address: '/test',
args: [
{
type: 'd',
value: 3.14159
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should encode double as float');
t.end();
});
tap.test('osc: blob argument with Buffer', (t) => {
// Test blob encoding with actual Buffer to ensure Buffer.isBuffer path is covered
const testBuffer = Buffer.from('test data');
const message = {
oscType: 'message',
address: '/test',
args: [testBuffer] // Direct Buffer without type wrapper
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
t.equal(decoded.args[0].value.toString(), 'test data', 'should preserve blob content');
t.end();
});
tap.test('osc: float number encoding', (t) => {
// Test encoding of float numbers to cover lines 132-133
const message = {
oscType: 'message',
address: '/test',
args: [3.14159] // Non-integer number should be encoded as float
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.ok(typeof decoded.args[0].value === 'number', 'should decode as number');
t.ok(!Number.isInteger(decoded.args[0].value), 'should be float, not integer');
t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should preserve float value');
t.end();
});
tap.test('osc: explicit integer type encoding', (t) => {
// Test explicit integer type to cover line 102
const message = {
oscType: 'message',
address: '/test',
args: [
{
type: 'i',
value: 42
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.args[0].value, 42, 'should encode and decode integer');
t.end();
});
tap.test('osc: explicit float type encoding', (t) => {
// Test explicit float type to cover line 105
const message = {
oscType: 'message',
address: '/test',
args: [
{
type: 'f',
value: 3.14
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.ok(Math.abs(decoded.args[0].value - 3.14) < 0.001, 'should encode and decode float');
t.end();
});
tap.test('osc: explicit string type encoding', (t) => {
// Test explicit string type to cover line 108
const message = {
oscType: 'message',
address: '/test',
args: [
{
type: 's',
value: 'hello'
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.args[0].value, 'hello', 'should encode and decode string');
t.end();
});
tap.test('osc: explicit blob type encoding', (t) => {
// Test explicit blob type to cover line 111
const testData = Buffer.from('blob data');
const message = {
oscType: 'message',
address: '/test',
args: [
{
type: 'b',
value: testData
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
t.equal(decoded.args[0].value.toString(), 'blob data', 'should preserve blob data');
t.end();
});
tap.test('osc: explicit boolean true type encoding', (t) => {
// Test explicit boolean true type to cover line 118
const message = {
oscType: 'message',
address: '/test',
args: [
{
type: 'T',
value: true
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.args[0].value, true, 'should encode and decode boolean true');
t.end();
});
tap.test('osc: MIDI type encoding with Buffer', (t) => {
// Test MIDI type with 4-byte Buffer
const midiData = Buffer.from([0x01, 0x90, 0x3C, 0x7F]); // port 1, note on, middle C, velocity 127
const message = {
oscType: 'message',
address: '/midi',
args: [
{
type: 'm',
value: midiData
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
t.equal(decoded.args[0].value.length, 4, 'should be 4 bytes');
t.equal(decoded.args[0].value[0], 0x01, 'port id should match');
t.equal(decoded.args[0].value[1], 0x90, 'status byte should match');
t.equal(decoded.args[0].value[2], 0x3C, 'data1 should match');
t.equal(decoded.args[0].value[3], 0x7F, 'data2 should match');
t.end();
});
tap.test('osc: MIDI type encoding with object', (t) => {
// Test MIDI type with object format
const message = {
oscType: 'message',
address: '/midi',
args: [
{
type: 'midi',
value: {
port: 2,
status: 0x80,
data1: 0x40,
data2: 0x00
}
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
t.equal(decoded.args[0].value[0], 2, 'port should match');
t.equal(decoded.args[0].value[1], 0x80, 'status should match');
t.equal(decoded.args[0].value[2], 0x40, 'data1 should match');
t.equal(decoded.args[0].value[3], 0x00, 'data2 should match');
t.end();
});
tap.test('osc: MIDI type with invalid buffer length', (t) => {
// Test MIDI type with wrong buffer length
const message = {
oscType: 'message',
address: '/midi',
args: [
{
type: 'm',
value: Buffer.from([0x90, 0x3C]) // Only 2 bytes
}
]
};
t.throws(() => {
_osc.toBuffer(message);
}, /MIDI message must be exactly 4 bytes/, 'should throw error for wrong buffer length');
t.end();
});
tap.test('osc: MIDI type with invalid value type', (t) => {
// Test MIDI type with invalid value
const message = {
oscType: 'message',
address: '/midi',
args: [
{
type: 'm',
value: 'invalid'
}
]
};
t.throws(() => {
_osc.toBuffer(message);
}, /MIDI value must be a 4-byte Buffer or object/, 'should throw error for invalid value type');
t.end();
});
tap.test('osc: MIDI type with partial object', (t) => {
// Test MIDI type with object having only some properties (should default others to 0)
const message = {
oscType: 'message',
address: '/midi',
args: [
{
type: 'm',
value: {
status: 0x90,
data1: 0x3C
// port and data2 should default to 0
}
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.args[0].value[0], 0, 'port should default to 0');
t.equal(decoded.args[0].value[1], 0x90, 'status should match');
t.equal(decoded.args[0].value[2], 0x3C, 'data1 should match');
t.equal(decoded.args[0].value[3], 0, 'data2 should default to 0');
t.end();
});
tap.test('osc: MIDI type decoding with insufficient buffer data', (t) => {
// Test the error case in readMidi when buffer doesn't have enough bytes
// This manually crafts a malformed OSC buffer with MIDI type tag but insufficient data
// Create a minimal OSC message buffer with MIDI type but truncated data
// OSC Format: address + typetags + arguments
// Address: "/m" (padded to 4 bytes)
const address = Buffer.from('/m\0\0', 'ascii'); // 4 bytes
// Type tags: ",m" (padded to 4 bytes)
const typeTags = Buffer.from(',m\0\0', 'ascii'); // 4 bytes
// MIDI data: only 2 bytes instead of required 4
const insufficientMidiData = Buffer.from([0x90, 0x3C]); // Only 2 bytes, need 4
// Combine into malformed OSC buffer
const malformedBuffer = Buffer.concat([address, typeTags, insufficientMidiData]);
t.throws(() => {
_osc.fromBuffer(malformedBuffer);
}, /Not enough bytes for MIDI message/, 'should throw error when MIDI data is truncated');
t.end();
});
tap.test('osc: MIDI type with falsy status and data1 values', (t) => {
// Test MIDI type with object having undefined/falsy status and data1 (should default to 0)
const message = {
oscType: 'message',
address: '/midi',
args: [
{
type: 'm',
value: {
port: 5,
data2: 0x40
// status and data1 are undefined, should default to 0
}
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.args[0].value[0], 5, 'port should match');
t.equal(decoded.args[0].value[1], 0, 'status should default to 0');
t.equal(decoded.args[0].value[2], 0, 'data1 should default to 0');
t.equal(decoded.args[0].value[3], 0x40, 'data2 should match');
t.end();
});
tap.test('osc: MIDI type with explicit zero status and data1 values', (t) => {
// Test MIDI type with object having explicit 0 values for status and data1
const message = {
oscType: 'message',
address: '/midi',
args: [
{
type: 'm',
value: {
port: 3,
status: 0,
data1: 0,
data2: 0x60
}
}
]
};
const buffer = _osc.toBuffer(message);
const decoded = _osc.fromBuffer(buffer);
t.equal(decoded.args[0].value[0], 3, 'port should match');
t.equal(decoded.args[0].value[1], 0, 'status should be 0');
t.equal(decoded.args[0].value[2], 0, 'data1 should be 0');
t.equal(decoded.args[0].value[3], 0x60, 'data2 should match');
t.end();
});
;