UNPKG

@clarify/node-red-contrib-clarify

Version:
405 lines (346 loc) 11.3 kB
import {describe, test, beforeEach, assert} from 'vitest'; import {fake, match, type SinonSpy} from 'sinon'; import { validToken, rpc, CLARIFY_STAGING_API_URL, insert, CLARIFY_CREDENTIALS, saveSignals, invalidToken, } from '@clarify/api-test-support'; import {InsertRPCMethod, SaveSignalsRPCMethod} from '@clarify/api'; import {setupNodeRed} from './utilities/setup-node-red'; import {waitUntil} from './utilities/wait-until'; import {setupMockServiceWorker} from './utilities/setup-msw'; import {setupFakeTimers} from './utilities/setup-fake-timers'; import {loadFlow, loadFlowWithoutAPI, loadFlowWithoutCredentials} from './utilities/load-flow'; import sinon from 'sinon'; let assertMatches = (expected: unknown) => { return match(payload => { try { assert.deepEqual(payload, expected); return true; } catch (error) { return false; } }); }; describe('clarify-insert', () => { let insertRequest!: SinonSpy<[InsertRPCMethod['parameters']]>; let saveRequest!: SinonSpy<[SaveSignalsRPCMethod['parameters']]>; let helper = setupNodeRed(); let server = setupMockServiceWorker(); let clock = setupFakeTimers(); describe('success', () => { beforeEach(async () => { let clarifyAPI = rpc.link(`${CLARIFY_STAGING_API_URL}rpc`); insertRequest = fake(); saveRequest = fake(); server.resetHandlers(validToken(), insert(clarifyAPI, insertRequest), saveSignals(clarifyAPI, saveRequest)); await loadFlow(helper); }); test('loads nodes', () => { let apiNode = helper.getNode('clarify-api-01'); let insertNode = helper.getNode('clarify-insert-01'); assert.ok(apiNode, 'Clarify API configuration loaded'); assert.ok(insertNode, 'Clarify Insert loaded'); }); describe('inserting data', () => { test('fires single request API', async () => { let insertNode = helper.getNode('clarify-insert-01'); insertNode.receive({ topic: 'clarify-input-id', payload: { times: [new Date().toISOString()], values: [1], }, }); await waitUntil(() => insertNode.status.calledOnce); assert.notOk(insertRequest.calledOnce); assert.ok(insertNode.status.calledOnce); await clock.runAllAsync(); await waitUntil(() => insertRequest.calledOnce && insertNode.status.calledThrice); assert.ok(insertNode.status.calledThrice); assert.equal(insertNode.error.callCount, 0); }); test('concatinates multiple topics', async () => { const DATE = '2022-05-27T08:00:00.000Z'; let insertNode = helper.getNode('clarify-insert-01'); insertNode.receive({ topic: 'clarify-input-id-01', payload: { times: [DATE], values: [1], }, }); insertNode.receive({ topic: 'clarify-input-id-02', payload: { times: [DATE], values: [2], }, }); await Promise.resolve(); assert.notOk(insertRequest.calledOnce); await clock.runAllAsync(); await waitUntil(() => insertRequest.calledOnce); assert.ok( insertRequest.calledWithExactly({ integration: CLARIFY_CREDENTIALS.integration, data: { times: [DATE], series: { 'clarify-input-id-01': [1], 'clarify-input-id-02': [2], }, }, }), ); assert.equal(insertNode.error.callCount, 0); }); test('concatinates multiple topics with non matching times', async () => { const DATE_1 = '2022-05-27T08:00:00.000Z'; const DATE_2 = '2022-05-27T08:01:00.000Z'; const DATE_3 = '2022-05-27T07:59:00.000Z'; let insertNode = helper.getNode('clarify-insert-01'); insertNode.receive({ topic: 'clarify-input-id-01', payload: { times: [DATE_1], values: [1], }, }); insertNode.receive({ topic: 'clarify-input-id-02', payload: { times: [DATE_2], values: [2], }, }); insertNode.receive({ topic: 'clarify-input-id-01', payload: { times: [DATE_3], values: [3], }, }); await Promise.resolve(); assert.notOk(insertRequest.calledOnce); await clock.runAllAsync(); await waitUntil(() => insertRequest.calledOnce); assert.ok( insertRequest.calledWithExactly({ integration: CLARIFY_CREDENTIALS.integration, data: { times: [DATE_3, DATE_1, DATE_2], series: { 'clarify-input-id-01': [3, 1, null], 'clarify-input-id-02': [null, null, 2], }, }, }), ); assert.equal(insertNode.error.callCount, 0); }); }); describe('saving signals', () => { test('saves signal', async () => { let insertNode = helper.getNode('clarify-insert-01'); insertNode.receive({ topic: 'clarify-input-id-01', signal: { name: 'Input id#01', }, }); await Promise.resolve(); assert.notOk(saveRequest.calledOnce); await clock.runAllAsync(); await waitUntil(() => saveRequest.calledOnce); assert.ok( saveRequest.calledWithExactly({ createOnly: false, integration: CLARIFY_CREDENTIALS.integration, inputs: { 'clarify-input-id-01': { name: 'Input id#01', }, }, }), ); assert.equal(insertNode.error.callCount, 0); }); test('saves signal after each other', async () => { let insertNode = helper.getNode('clarify-insert-01'); insertNode.receive({ topic: 'clarify-input-id-04', signal: { name: 'Input id#01', }, }); await Promise.resolve(); assert.notOk(saveRequest.calledOnce); await clock.runAllAsync(); await waitUntil(() => saveRequest.calledOnce); insertNode.receive({ topic: 'clarify-input-id-04', signal: { name: 'Input id#02', }, }); await clock.runAllAsync(); await waitUntil(() => saveRequest.calledTwice); }); }); }); describe('invalid payload', () => { beforeEach(async () => { await loadFlow(helper); }); test.each([ [ 'without topic', { signal: { name: 'Signal name', }, }, ], [ 'with invalid topic', { topic: 'topic id with space', signal: { name: 'Signal name', }, }, ], [ 'with invalid payload', { topic: 'valid-id', payload: { times: ['2022-05-29P13:24:44.837Z'], series: [1], }, }, ], [ 'with not the same number of series as times', { topic: 'valid-id', payload: { times: [new Date().toISOString()], series: [1, 2], }, }, ], ])('with invalid payload (%s)', async (_name, payload) => { let insertNode = helper.getNode('clarify-insert-01'); insertNode.receive(payload); await clock.runAllAsync(); await waitUntil(() => insertNode.error.calledOnce); assert.ok(insertNode.status.called, 'Invokes error callback'); }); }); describe('failure (invalid token)', () => { beforeEach(async () => { let clarifyAPI = rpc.link(`${CLARIFY_STAGING_API_URL}rpc`); insertRequest = fake(); saveRequest = fake(); server.resetHandlers(invalidToken(), insert(clarifyAPI, insertRequest), saveSignals(clarifyAPI, saveRequest)); }); describe('revoked credentials', () => { beforeEach(async () => { await loadFlow(helper); }); test('clarify-insert node sends error when flushing data frames', async () => { let insertNode = helper.getNode('clarify-insert-01'); const INSERT_DATE = new Date().toISOString(); insertNode.receive({ topic: 'clarify-input-id', payload: { times: [INSERT_DATE], values: [1], }, }); await waitUntil(() => insertNode.status.calledOnce); assert.notOk(insertRequest.calledOnce); assert.ok(insertNode.status.calledOnce); await clock.runAllAsync(); await waitUntil(() => insertNode.error.calledOnce); sinon.assert.calledWithMatch( insertNode.error, assertMatches('Unknown payload'), assertMatches({ payload: { times: [INSERT_DATE], series: {'clarify-input-id': [1]}, }, }), ); }); test('clarify-insert node sends saving signals', async () => { let insertNode = helper.getNode('clarify-insert-01'); insertNode.receive({ topic: 'clarify-input-id', signal: { name: 'Clarify input id #456', }, }); await waitUntil(() => insertNode.status.calledOnce); assert.notOk(saveRequest.calledOnce); assert.ok(insertNode.status.calledOnce); await clock.runAllAsync(); await waitUntil(() => insertNode.error.calledOnce); sinon.assert.calledWithMatch( insertNode.error, assertMatches('Unknown payload'), assertMatches({ payload: { 'clarify-input-id': { name: 'Clarify input id #456', }, }, }), ); }); }); describe('without API node', () => { beforeEach(async () => { await loadFlowWithoutAPI(helper); }); test('sets status ', async () => { let insertNode = helper.getNode('clarify-insert-01'); assert.notOk(insertNode.status.called, 'Refrains from setting status when not configured'); insertNode.receive({ topic: 'clarify-input-id', signal: { name: 'Clarify input id #456', }, }); await clock.runAllAsync(); await waitUntil(() => insertNode.status.calledOnce); assert.ok(insertNode.error.called, 'Invokes error callback'); }); }); describe('without credentials', () => { beforeEach(async () => { await loadFlowWithoutCredentials(helper); }); test('sets status when receiving ', async () => { let insertNode = helper.getNode('clarify-insert-01'); assert.ok(insertNode.status.calledOnce, 'Immediately sets status'); insertNode.receive({ topic: 'clarify-input-id', signal: { name: 'Clarify input id #456', }, }); await clock.runAllAsync(); await waitUntil(() => insertNode.status.calledTwice); assert.ok(insertNode.error.called, 'Invokes error callback'); }); }); }); });