@fiqch/n8n-nodes-fiq-opcua
Version:
n8n custom node for reading and writing OPC UA data (FactoryIQ)
1,042 lines • 48.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const OpcUa_1 = require("../nodes/FactoryIQ/OpcUa");
const mockSession = {
readVariableValue: jest.fn().mockResolvedValue([
{ statusCode: { name: 'Good' }, value: { value: 42, dataType: 'Double' } },
]),
read: jest.fn().mockResolvedValue([
{ statusCode: { name: 'Good' }, value: { value: 42, dataType: 'Double' } },
]),
write: jest.fn().mockResolvedValue([{ name: 'Good' }]),
call: jest.fn().mockResolvedValue([
{ statusCode: { name: 'Good' }, outputArguments: [{ value: 'Hello' }] },
]),
close: jest.fn().mockResolvedValue(undefined),
isChannelValid: jest.fn().mockReturnValue(true),
isReconnecting: false,
};
const mockPool = {
getConnection: jest.fn().mockResolvedValue({
client: {},
session: mockSession,
inUse: false,
key: 'test-key',
}),
releaseConnection: jest.fn(),
};
jest.mock('../nodes/FactoryIQ/OpcUa/ConnectionPool', () => ({
OpcUaConnectionPool: {
getInstance: jest.fn(() => mockPool),
},
}));
jest.mock('../vendor', () => ({
OPCUAClient: {
create: jest.fn(() => ({
connect: jest.fn().mockResolvedValue(undefined),
disconnect: jest.fn().mockResolvedValue(undefined),
createSession: jest.fn().mockResolvedValue({
readVariableValue: jest.fn().mockResolvedValue([
{ statusCode: { name: 'Good' }, value: { value: 42, dataType: 'Double' } },
]),
read: jest.fn().mockResolvedValue([
{ statusCode: { name: 'Good' }, value: { value: 42, dataType: 'Double' } },
]),
write: jest.fn().mockResolvedValue([{ name: 'Good' }]),
call: jest.fn().mockResolvedValue([
{ statusCode: { name: 'Good' }, outputArguments: [{ value: 'Hello' }] },
]),
close: jest.fn().mockResolvedValue(undefined),
isChannelValid: jest.fn().mockReturnValue(true),
isReconnecting: false,
}),
})),
},
SecurityPolicy: { None: 'None' },
MessageSecurityMode: { None: 'None' },
UserTokenType: { UserName: 'UserName', Certificate: 'Certificate' },
DataType: { Double: 'Double', String: 'String' },
AttributeIds: { Value: 13 },
}));
function createMockContext(params, credentials) {
const isWriter = params.operation === 'write';
return {
getNodeParameter: (name, itemIndex, fallback) => {
if (params[name] !== undefined)
return params[name];
if (name === 'writeOperation' && isWriter)
return params.writeOperation || 'writeVariable';
if (name === 'operation')
return params.operation;
if (name === 'nodeIds' && params.operation === 'read')
return params.nodeIds || [];
return fallback;
},
getCredentials: async (name) => credentials,
getInputData: () => isWriter ? [{}] : [],
getNode: () => ({}),
};
}
function setDefaultNodeOpcuaMock() {
jest.resetModules();
jest.doMock('../vendor', () => ({
OPCUAClient: {
create: jest.fn(() => ({
connect: jest.fn().mockResolvedValue(undefined),
disconnect: jest.fn().mockResolvedValue(undefined),
createSession: jest.fn().mockResolvedValue({
readVariableValue: jest.fn().mockResolvedValue([
{ statusCode: { name: "Good" }, value: { value: 42, dataType: "Double" } }
]),
read: jest.fn().mockResolvedValue([
{ statusCode: { name: "Good" }, value: { value: 42, dataType: "Double" } }
]),
write: jest.fn().mockResolvedValue([{ name: "Good" }]),
call: jest.fn().mockResolvedValue([{ statusCode: { name: "Good" }, outputArguments: [{ value: "Hello" }] }]),
close: jest.fn().mockResolvedValue(undefined),
}),
})),
},
SecurityPolicy: { None: "None", Basic128Rsa15: "Basic128Rsa15", Basic256: "Basic256", Basic256Sha256: "Basic256Sha256", Aes128_Sha256_RsaOaep: "Aes128_Sha256_RsaOaep", Aes256_Sha256_RsaPss: "Aes256_Sha256_RsaPss" },
MessageSecurityMode: { None: "None", Sign: "Sign", SignAndEncrypt: "SignAndEncrypt" },
UserTokenType: { UserName: "UserName", Certificate: "Certificate" },
DataType: { Double: "Double", String: "String", Boolean: "Boolean", SByte: "SByte", Byte: "Byte", Int16: "Int16", UInt16: "UInt16", Int32: "Int32", UInt32: "UInt32", Int64: "Int64", UInt64: "UInt64", Float: "Float", DateTime: "DateTime", Guid: "Guid", ByteString: "ByteString" },
AttributeIds: { Value: 13 },
}));
}
setDefaultNodeOpcuaMock();
describe("FactoryIQ OpcUA Node (unit, with mocks)", () => {
beforeEach(() => {
setDefaultNodeOpcuaMock();
jest.clearAllMocks();
mockPool.getConnection.mockResolvedValue({
client: {},
session: mockSession,
inUse: false,
key: 'test-key',
});
mockSession.readVariableValue.mockResolvedValue([
{ statusCode: { name: 'Good' }, value: { value: 42, dataType: 'Double' } },
]);
mockSession.read.mockResolvedValue([
{ statusCode: { name: 'Good' }, value: { value: 42, dataType: 'Double' } },
]);
mockSession.write.mockResolvedValue([{ name: 'Good' }]);
mockSession.call.mockResolvedValue([
{ statusCode: { name: 'Good' }, outputArguments: [{ value: 'Hello' }] },
]);
});
it("returns correct output in Reader mode", async () => {
var _a;
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
const result = await node.execute.call(context);
const metrics = ((_a = result[0][0].json.metrics) !== null && _a !== void 0 ? _a : {});
expect(metrics["ns=1;s=TestVariable"]).toBe(42);
expect(result[0][0].json.status).toBe("Good");
});
it("returns correct output in Writer mode (writeVariable)", async () => {
var _a;
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=WritableVariable",
value: "123.45",
dataType: "Double",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
const result = await node.execute.call(context);
const metrics = ((_a = result[0][0].json.metrics) !== null && _a !== void 0 ? _a : {});
expect(metrics["ns=1;s=WritableVariable"]).toBe(123.45);
expect(result[0][0].json.status).toBe("ok");
});
it("returns correct output in Writer mode (callMethod)", async () => {
var _a;
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "callMethod",
objectNodeId: "ns=1;s=Objects",
methodNodeId: "ns=1;s=TestMethod",
parameters: {},
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
const result = await node.execute.call(context);
expect(((_a = result[0][0].json.metrics) !== null && _a !== void 0 ? _a : {}).outputArguments[0].value).toBe("Hello");
expect(result[0][0].json.status).toBe("ok");
});
it("handles errors gracefully", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockPool.getConnection.mockRejectedValue(new Error("Connection failed"));
await expect(node.execute.call(context)).rejects.toThrow("Failed to connect or authenticate to OPC UA server.");
});
it("throws if credentials are missing", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable"],
};
const context = createMockContext(params, null);
await expect(node.execute.call(context)).rejects.toThrow("No OPC UA credentials provided.");
});
it("throws on invalid mode", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "invalidMode",
nodeIds: ["ns=1;s=TestVariable"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
await expect(node.execute.call(context)).rejects.toThrow("Invalid operation selected.");
});
describe("Read operation edge cases", () => {
it("should throw error when no nodeIds provided", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: [],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
await expect(node.execute.call(context)).rejects.toThrow("At least one Node ID must be provided for reading.");
});
it("should throw error when nodeIds is empty string", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: [""],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
await expect(node.execute.call(context)).rejects.toThrow("At least one Node ID must be provided for reading.");
});
it("should handle single nodeId as string", async () => {
var _a;
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: "ns=1;s=TestVariable",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
const result = await node.execute.call(context);
const metrics = ((_a = result[0][0].json.metrics) !== null && _a !== void 0 ? _a : {});
expect(metrics["ns=1;s=TestVariable"]).toBe(42);
});
it("should handle multiple nodeIds with consolidated output", async () => {
var _a, _b;
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable1", "ns=1;s=TestVariable2"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.read.mockResolvedValue([
{ statusCode: { name: "Good" }, value: { value: 42, dataType: 7 } },
{ statusCode: { name: "Good" }, value: { value: 99, dataType: 7 } },
]);
const result = await node.execute.call(context);
expect(result[0]).toHaveLength(1);
const output = result[0][0].json;
const metrics = ((_a = output.metrics) !== null && _a !== void 0 ? _a : {});
const meta = ((_b = output.meta) !== null && _b !== void 0 ? _b : {});
expect(metrics["ns=1;s=TestVariable1"]).toBe(42);
expect(metrics["ns=1;s=TestVariable2"]).toBe(99);
expect(output.status).toBe("Good");
expect(meta.nodeCount).toBe(2);
expect(meta.nodeIds).toEqual(["ns=1;s=TestVariable1", "ns=1;s=TestVariable2"]);
expect(meta.dataTypes["ns=1;s=TestVariable1"]).toBe("UInt32");
expect(meta.dataTypes["ns=1;s=TestVariable2"]).toBe("UInt32");
});
it("should handle read errors with bad status code", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.read.mockResolvedValue([
{ statusCode: { name: "BadNodeIdUnknown" }, value: null }
]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("BadNodeIdUnknown");
expect(result[0][0].json.meta.error).toBe("BadNodeIdUnknown");
expect(result[0][0].json.metrics["ns=1;s=TestVariable"]).toBeNull();
});
it("should throw error for x509 authentication without certificate or private key", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "x509",
};
const context = createMockContext(params, credentials);
await expect(node.execute.call(context)).rejects.toThrow("X509 authentication requires both certificate and private key.");
});
it("should handle session read failure", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.read.mockRejectedValue(new Error("Read failed"));
await expect(node.execute.call(context)).rejects.toThrow("Failed to read node values.");
});
});
describe("Write operation edge cases", () => {
it("should throw error when writeOperation is not specified", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: undefined,
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
await expect(node.execute.call(context)).rejects.toThrow("Node ID and Data Type are required for variable write.");
});
it("should throw error when nodeId is missing for writeVariable", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
dataType: "Double",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
await expect(node.execute.call(context)).rejects.toThrow("Node ID and Data Type are required for variable write.");
});
it("should throw error when dataType is missing for writeVariable", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
await expect(node.execute.call(context)).rejects.toThrow("Node ID and Data Type are required for variable write.");
});
it("should handle write variable with different data types", async () => {
var _a;
const dataTypes = ["Boolean", "String", "Int32", "Float"];
const values = ["true", "Hello", "123", "123.45"];
const expectedValues = [true, "Hello", 123, 123.45];
for (let i = 0; i < dataTypes.length; i++) {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: values[i],
dataType: dataTypes[i],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
const result = await node.execute.call(context);
const metrics = ((_a = result[0][0].json.metrics) !== null && _a !== void 0 ? _a : {});
expect(metrics["ns=1;s=TestVariable"]).toBe(expectedValues[i]);
}
});
it("should handle method call with parameters", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "callMethod",
objectNodeId: "ns=1;s=Objects",
methodNodeId: "ns=1;s=TestMethod",
parameters: {
argument: [
{ dataType: "String", value: "test" },
{ dataType: "Int32", value: "42" }
]
},
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
});
it("should handle write errors during session write", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.write.mockRejectedValue(new Error("Write failed"));
await expect(node.execute.call(context)).rejects.toThrow("Failed to execute operation on OPC UA node.");
});
it("should handle method call errors", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "callMethod",
objectNodeId: "ns=1;s=Objects",
methodNodeId: "ns=1;s=TestMethod",
parameters: {},
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.call.mockRejectedValue(new Error("Method call failed"));
await expect(node.execute.call(context)).rejects.toThrow("Failed to execute operation on OPC UA node.");
});
});
describe("Data type conversion", () => {
it("should handle all data type conversions in convertValueToDataType", () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const convertValueToDataType = node.constructor.convertValueToDataType;
expect(convertValueToDataType("true", "Boolean")).toBe(true);
expect(convertValueToDataType("false", "Boolean")).toBe(false);
expect(convertValueToDataType("1", "Boolean")).toBe(true);
expect(convertValueToDataType("0", "Boolean")).toBe(false);
expect(convertValueToDataType("123", "SByte")).toBe(123);
expect(convertValueToDataType("123", "Byte")).toBe(123);
expect(convertValueToDataType("123", "Int16")).toBe(123);
expect(convertValueToDataType("123", "UInt16")).toBe(123);
expect(convertValueToDataType("123", "Int32")).toBe(123);
expect(convertValueToDataType("123", "UInt32")).toBe(123);
expect(convertValueToDataType("123", "Int64")).toBe(123);
expect(convertValueToDataType("123", "UInt64")).toBe(123);
expect(convertValueToDataType("123.45", "Float")).toBe(123.45);
expect(convertValueToDataType("123.45", "Double")).toBe(123.45);
expect(convertValueToDataType("Hello", "String")).toBe("Hello");
expect(Buffer.isBuffer(convertValueToDataType("test", "ByteString"))).toBe(true);
expect(convertValueToDataType("test", "Guid")).toBe("test");
expect(convertValueToDataType("2023-01-01", "DateTime")).toBeInstanceOf(Date);
expect(convertValueToDataType("test", "Unknown")).toBe("test");
});
});
describe("Multiple input items for write operations", () => {
function createMockContextWithMultipleItems(params, credentials, itemCount) {
const items = Array(itemCount).fill({});
return {
getNodeParameter: (name, itemIndex, fallback) => {
if (params[name] !== undefined)
return params[name];
if (name === 'writeOperation')
return params.writeOperation || 'writeVariable';
if (name === 'operation')
return params.operation;
return fallback;
},
getCredentials: async (name) => credentials,
getInputData: () => items,
getNode: () => ({}),
};
}
it("should handle multiple input items for write operations", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContextWithMultipleItems(params, credentials, 3);
const result = await node.execute.call(context);
expect(result[0]).toHaveLength(3);
});
});
describe("Direct connection fallback and cleanup paths", () => {
it("should handle pool connection failure", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockPool.getConnection.mockRejectedValue(new Error("Pool failed completely"));
await expect(node.execute.call(context)).rejects.toThrow("Failed to connect or authenticate to OPC UA server.");
});
it("should handle unsupported writeOperation", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "unsupportedOperation",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
await expect(node.execute.call(context)).rejects.toThrow("Unsupported operation type.");
});
it("should handle write operation with error status result", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.write.mockResolvedValue([{ name: "BadNodeIdUnknown" }]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("error");
});
it("should handle method call with error status result", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "callMethod",
objectNodeId: "ns=1;s=Objects",
methodNodeId: "ns=1;s=TestMethod",
parameters: {},
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.call.mockResolvedValue([{ statusCode: { name: "BadMethodInvalid" } }]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("error");
});
it("should handle operation execution failure", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.write.mockRejectedValue(new Error("Write operation failed"));
await expect(node.execute.call(context)).rejects.toThrow("Failed to execute operation on OPC UA node.");
});
it("should handle direct connection cleanup when pool returns null", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockPool.getConnection.mockResolvedValue(null);
await expect(node.execute.call(context)).rejects.toThrow();
});
it("should handle direct connection cleanup with session close error", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
const mockDirectSession = {
write: jest.fn().mockResolvedValue([{ name: "Good" }]),
close: jest.fn().mockRejectedValue(new Error("Session close failed")),
};
const mockDirectClient = {
disconnect: jest.fn().mockResolvedValue(undefined),
};
mockPool.getConnection.mockResolvedValue({
client: mockDirectClient,
session: mockDirectSession,
inUse: false,
key: 'direct-connection',
});
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
});
it("should handle direct connection cleanup with client disconnect error", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
const mockDirectSession = {
write: jest.fn().mockResolvedValue([{ name: "Good" }]),
close: jest.fn().mockResolvedValue(undefined),
};
const mockDirectClient = {
disconnect: jest.fn().mockRejectedValue(new Error("Client disconnect failed")),
};
mockPool.getConnection.mockResolvedValue({
client: mockDirectClient,
session: mockDirectSession,
inUse: false,
key: 'direct-connection',
});
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
});
});
describe("Data type mapping", () => {
it("should have correct opcuaDataTypeMap", () => {
const opcuaDataTypeMap = OpcUa_1.FactoryiqOpcUa.opcuaDataTypeMap;
expect(opcuaDataTypeMap[1]).toBe("Boolean");
expect(opcuaDataTypeMap[2]).toBe("SByte");
expect(opcuaDataTypeMap[3]).toBe("Byte");
expect(opcuaDataTypeMap[4]).toBe("Int16");
expect(opcuaDataTypeMap[5]).toBe("UInt16");
expect(opcuaDataTypeMap[6]).toBe("Int32");
expect(opcuaDataTypeMap[7]).toBe("UInt32");
expect(opcuaDataTypeMap[8]).toBe("Int64");
expect(opcuaDataTypeMap[9]).toBe("UInt64");
expect(opcuaDataTypeMap[10]).toBe("Float");
expect(opcuaDataTypeMap[11]).toBe("Double");
expect(opcuaDataTypeMap[12]).toBe("String");
expect(opcuaDataTypeMap[13]).toBe("DateTime");
expect(opcuaDataTypeMap[14]).toBe("Guid");
expect(opcuaDataTypeMap[15]).toBe("ByteString");
});
it("should handle read result with numeric dataType", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.read.mockResolvedValue([
{ statusCode: { name: "Good" }, value: { value: 42, dataType: 11 } }
]);
const result = await node.execute.call(context);
expect(result[0][0].json.meta.dataType).toBe("Double");
});
it("should handle read result with null or unknown dataType", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.read.mockResolvedValue([
{ statusCode: { name: "Good" }, value: { value: 42, dataType: 999 } }
]);
const result = await node.execute.call(context);
expect(result[0][0].json.meta.dataType).toBeNull();
});
it("should handle read result with no value", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "read",
nodeIds: ["ns=1;s=TestVariable"],
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.read.mockResolvedValue([
{ statusCode: { name: "Good" }, value: null }
]);
const result = await node.execute.call(context);
expect(result[0][0].json.metrics["ns=1;s=TestVariable"]).toBeNull();
expect(result[0][0].json.meta.dataType).toBeNull();
});
});
describe("Additional edge cases for complete coverage", () => {
it("should handle conversion of different data types in convertValueToDataType", () => {
const convertMethod = OpcUa_1.FactoryiqOpcUa.convertValueToDataType;
expect(convertMethod("true", "Boolean")).toBe(true);
expect(convertMethod("TRUE", "Boolean")).toBe(true);
expect(convertMethod("1", "Boolean")).toBe(true);
expect(convertMethod("on", "Boolean")).toBe(true);
expect(convertMethod("ON", "Boolean")).toBe(true);
expect(convertMethod("yes", "Boolean")).toBe(true);
expect(convertMethod("YES", "Boolean")).toBe(true);
expect(convertMethod("false", "Boolean")).toBe(false);
expect(convertMethod("0", "Boolean")).toBe(false);
expect(convertMethod("no", "Boolean")).toBe(false);
expect(convertMethod("200", "SByte")).toBe(127);
expect(convertMethod("-200", "SByte")).toBe(-128);
expect(convertMethod("300", "Byte")).toBe(255);
expect(convertMethod("-10", "Byte")).toBe(0);
expect(convertMethod("70000", "Int16")).toBe(32767);
expect(convertMethod("-70000", "Int16")).toBe(-32768);
expect(convertMethod("70000", "UInt16")).toBe(65535);
expect(convertMethod("-10", "UInt16")).toBe(0);
expect(convertMethod("5000000000", "Int32")).toBe(2147483647);
expect(convertMethod("-5000000000", "Int32")).toBe(-2147483648);
expect(convertMethod("5000000000", "UInt32")).toBe(4294967295);
expect(convertMethod("invalid", "Int64")).toBe(0);
expect(convertMethod("invalid", "UInt64")).toBe(0);
expect(convertMethod("-100", "UInt64")).toBe(0);
expect(convertMethod("3.14159", "Float")).toBe(3.14159);
expect(convertMethod("invalid", "Float")).toBe(0.0);
expect(convertMethod("2.71828", "Double")).toBe(2.71828);
expect(convertMethod("invalid", "Double")).toBe(0.0);
const dateStr = "2023-01-01T00:00:00Z";
const result = convertMethod(dateStr, "DateTime");
expect(result).toBeInstanceOf(Date);
expect(convertMethod("test-guid", "Guid")).toBe("test-guid");
const byteStringResult = convertMethod("hello", "ByteString");
expect(Buffer.isBuffer(byteStringResult)).toBe(true);
expect(byteStringResult.toString()).toBe("hello");
expect(convertMethod("test", "UnknownType")).toBe("test");
});
it("should handle getDataTypeEnum for all data types indirectly through write operations", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "true",
dataType: "Boolean",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.write.mockResolvedValue([{ name: "Good" }]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
expect(result[0][0].json.meta.operationType).toBe("variable_write");
});
it("should handle write operation with complex parameters", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "callMethod",
objectNodeId: "ns=1;s=Objects",
methodNodeId: "ns=1;s=TestMethod",
parameters: {
arguments: [
{ dataType: "String", value: "test" },
{ dataType: "Int32", value: 42 },
{ dataType: "Boolean", value: true }
]
},
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.call.mockResolvedValue([{
statusCode: { name: "Good" },
outputArguments: ["result1", 123]
}]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
expect(result[0][0].json.metrics.outputArguments).toEqual(["result1", 123]);
expect(result[0][0].json.meta.inputArguments).toEqual([
{ dataType: "String", value: "test" },
{ dataType: "Int32", value: 42 },
{ dataType: "Boolean", value: true }
]);
});
it("should handle write operation with empty parameters", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "callMethod",
objectNodeId: "ns=1;s=Objects",
methodNodeId: "ns=1;s=TestMethod",
parameters: {},
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.call.mockResolvedValue([{
statusCode: { name: "Good" },
outputArguments: []
}]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
expect(result[0][0].json.metrics.outputArguments).toEqual([]);
expect(result[0][0].json.meta.inputArguments).toEqual([]);
});
it("should handle write operation with non-array parameters", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "callMethod",
objectNodeId: "ns=1;s=Objects",
methodNodeId: "ns=1;s=TestMethod",
parameters: { arguments: "not-an-array" },
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.call.mockResolvedValue([{
statusCode: { name: "Good" },
outputArguments: []
}]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
expect(result[0][0].json.meta.inputArguments).toEqual([]);
});
it("should handle different status code formats in write operations", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: "123",
dataType: "Int32",
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.write.mockResolvedValue([{ toString: () => "Good" }]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
expect(result[0][0].json.meta.statusCode).toBe("Good");
});
it("should handle different status code formats in method calls", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const params = {
operation: "write",
writeOperation: "callMethod",
objectNodeId: "ns=1;s=Objects",
methodNodeId: "ns=1;s=TestMethod",
parameters: {},
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.call.mockResolvedValue([{
statusCode: { toString: () => "Good" },
outputArguments: []
}]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
expect(result[0][0].json.meta.statusCode).toBe("Good");
});
it("should handle write operation with all supported data types", async () => {
const node = new OpcUa_1.FactoryiqOpcUa();
const dataTypes = [
"Boolean", "SByte", "Byte", "Int16", "UInt16", "Int32", "UInt32",
"Int64", "UInt64", "Float", "Double", "String", "DateTime", "Guid", "ByteString"
];
for (const dataType of dataTypes) {
const params = {
operation: "write",
writeOperation: "writeVariable",
nodeId: "ns=1;s=TestVariable",
value: dataType === "Boolean" ? "true" : "123",
dataType,
};
const credentials = {
endpointUrl: "opc.tcp://localhost:4840",
securityPolicy: "None",
securityMode: "None",
authenticationType: "anonymous",
};
const context = createMockContext(params, credentials);
mockSession.write.mockResolvedValue([{ name: "Good" }]);
const result = await node.execute.call(context);
expect(result[0][0].json.status).toBe("ok");
expect(result[0][0].json.meta.operationType).toBe("variable_write");
}
});
});
});
//# sourceMappingURL=OpcUa.node.unit.test.js.map