From f65a82adf3e0856691261f6dc6ce360a13d8fc9d Mon Sep 17 00:00:00 2001 From: fosky94 Date: Tue, 19 May 2026 17:26:02 -0500 Subject: [PATCH 1/5] Create Client side metrics for redis --- .../redis/client_side_metrics/server.js | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 memorystore/redis/client_side_metrics/server.js diff --git a/memorystore/redis/client_side_metrics/server.js b/memorystore/redis/client_side_metrics/server.js new file mode 100644 index 00000000000..6dcae76428a --- /dev/null +++ b/memorystore/redis/client_side_metrics/server.js @@ -0,0 +1,99 @@ +'use strict'; + +const { trace, metrics } = require('@opentelemetry/api'); +const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); +const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base'); +const { TraceExporter } = require('@google-cloud/opentelemetry-cloud-trace-exporter'); +const { MeterProvider, PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); +const { MetricExporter } = require('@google-cloud/opentelemetry-cloud-monitoring-exporter'); +const { RedisInstrumentation } = require('@opentelemetry/instrumentation-redis-4'); +const { registerInstrumentations } = require('@opentelemetry/instrumentation'); +const { performance } = require('perf_hooks'); + +const provider = new NodeTracerProvider(); +provider.addSpanProcessor(new BatchSpanProcessor(new TraceExporter())); +provider.register(); + +registerInstrumentations({ + instrumentations: [new RedisInstrumentation()] +}); + +const redis = require('redis'); + +const metricExporter = new MetricExporter(); +const metricReader = new PeriodicExportingMetricReader({ exporter: metricExporter, exportIntervalMillis: 10000 }); +const meterProvider = new MeterProvider({ readers: [metricReader] }); +metrics.setGlobalMeterProvider(meterProvider); + +const tracer = trace.getTracer('redis.client.node'); +const meter = metrics.getMeter('redis.metrics.node'); + +const rttHist = meter.createHistogram('redis_client_rtt', { unit: 'ms' }); +const appBlockHist = meter.createHistogram('redis_application_blocking_latency', { unit: 'ms' }); +const retryCounter = meter.createCounter('redis_retry_count'); +const connErrorCounter = meter.createCounter('redis_connectivity_error_count'); + +retryCounter.add(0, { operation: 'startup' }); +connErrorCounter.add(0, { operation: 'startup' }); + +const REDISHOST = process.env.REDISHOST || 'localhost'; +const REDISPORT = process.env.REDISPORT || 6379; + +const client = redis.createClient({ + socket: { + host: REDISHOST, + port: REDISPORT, + reconnectStrategy: (retries) => { + connErrorCounter.add(1, { error: 'socket_reconnect' }); + if (retries > 5) return new Error('Max retries reached'); + return Math.min(retries * 100, 3000); + } + } +}); +client.on('error', (err) => console.log('Redis Client Error', err)); + +async function smartRedisCall(operationName, func, ...args) { + let attempt = 0; + while (attempt < 3) { + try { + const reqStart = performance.now(); + const response = await func(...args); + rttHist.record(performance.now() - reqStart, { operation: operationName }); + + const appParseStart = performance.now(); + const _ = String(response); + appBlockHist.record(performance.now() - appParseStart, { operation: operationName }); + + return response; + } catch (e) { + attempt++; + retryCounter.add(1, { operation: operationName }); + if (attempt >= 3) throw e; + await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100)); + } + } +} + +async function main() { + await client.connect(); + + await tracer.startActiveSpan('process_user_span', async (span) => { + try { + // Simple write and read operations + await smartRedisCall('set_user', client.set.bind(client), 'user:123', 'active'); + + const result = await smartRedisCall('get_user', client.get.bind(client), 'user:123'); + console.log('Retrieved:', result); + } catch (e) { + span.recordException(e); + } finally { + span.end(); + } + }); + + await client.quit(); + await provider.forceFlush(); + await meterProvider.forceFlush(); +} + +main().catch(console.error); From 9285f3fea0729b1af68783fb834efc819046f380 Mon Sep 17 00:00:00 2001 From: fosky94 Date: Tue, 19 May 2026 18:07:27 -0500 Subject: [PATCH 2/5] Update license --- memorystore/redis/client_side_metrics/server.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/memorystore/redis/client_side_metrics/server.js b/memorystore/redis/client_side_metrics/server.js index 6dcae76428a..249f40f4a63 100644 --- a/memorystore/redis/client_side_metrics/server.js +++ b/memorystore/redis/client_side_metrics/server.js @@ -1,3 +1,18 @@ +/** + * Copyright 2026 Google, Inc. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + 'use strict'; const { trace, metrics } = require('@opentelemetry/api'); From b48722680a7f40a42c7836f2bb00959689de7d49 Mon Sep 17 00:00:00 2001 From: fosky94 Date: Tue, 9 Jun 2026 17:10:11 -0500 Subject: [PATCH 3/5] feat(redis): create client side metrics for redis --- memorystore/redis/client_side_metrics/server.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/memorystore/redis/client_side_metrics/server.js b/memorystore/redis/client_side_metrics/server.js index 249f40f4a63..afd879e7168 100644 --- a/memorystore/redis/client_side_metrics/server.js +++ b/memorystore/redis/client_side_metrics/server.js @@ -13,6 +13,8 @@ * limitations under the License. */ +// [START memorystore_redis_client_side_metrics] + 'use strict'; const { trace, metrics } = require('@opentelemetry/api'); @@ -112,3 +114,4 @@ async function main() { } main().catch(console.error); +// [END memorystore_redis_client_side_metrics] From 97d688e750e2ca641b520a981af606f10928155b Mon Sep 17 00:00:00 2001 From: fosky94 Date: Thu, 9 Jul 2026 19:14:11 +0000 Subject: [PATCH 4/5] fix:fixing missing dependencies and add tests --- .../redis/client_side_metrics/package.json | 36 +++++++ .../redis/client_side_metrics/server.js | 22 +++- .../client_side_metrics/test/server.test.js | 101 ++++++++++++++++++ 3 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 memorystore/redis/client_side_metrics/package.json create mode 100644 memorystore/redis/client_side_metrics/test/server.test.js diff --git a/memorystore/redis/client_side_metrics/package.json b/memorystore/redis/client_side_metrics/package.json new file mode 100644 index 00000000000..4ccec3a57a6 --- /dev/null +++ b/memorystore/redis/client_side_metrics/package.json @@ -0,0 +1,36 @@ +{ + "name": "memorystore-redis-client-side-metrics", + "description": "An example of using Memorystore (Redis) with OpenTelemetry for client-side metrics and tracing in Node.js", + "version": "0.0.1", + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "engines": { + "node": ">=18.0.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "scripts": { + "test": "mocha test/**/*.js" + }, + "dependencies": { + "@google-cloud/opentelemetry-cloud-monitoring-exporter": "^0.21.0", + "@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/instrumentation": "^0.205.0", + "@opentelemetry/instrumentation-redis": "^0.67.0", + "@opentelemetry/resources": "^2.1.0", + "@opentelemetry/sdk-metrics": "^2.1.0", + "@opentelemetry/sdk-trace-base": "^2.1.0", + "@opentelemetry/sdk-trace-node": "^2.1.0", + "redis": "^4.6.0" + }, + "devDependencies": { + "mocha": "^10.0.0", + "chai": "^4.3.0", + "proxyquire": "^2.1.0", + "sinon": "^15.0.0" + } +} \ No newline at end of file diff --git a/memorystore/redis/client_side_metrics/server.js b/memorystore/redis/client_side_metrics/server.js index afd879e7168..b128d4533fe 100644 --- a/memorystore/redis/client_side_metrics/server.js +++ b/memorystore/redis/client_side_metrics/server.js @@ -23,12 +23,14 @@ const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base'); const { TraceExporter } = require('@google-cloud/opentelemetry-cloud-trace-exporter'); const { MeterProvider, PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); const { MetricExporter } = require('@google-cloud/opentelemetry-cloud-monitoring-exporter'); -const { RedisInstrumentation } = require('@opentelemetry/instrumentation-redis-4'); +const { RedisInstrumentation } = require('@opentelemetry/instrumentation-redis'); const { registerInstrumentations } = require('@opentelemetry/instrumentation'); const { performance } = require('perf_hooks'); -const provider = new NodeTracerProvider(); -provider.addSpanProcessor(new BatchSpanProcessor(new TraceExporter())); +// FIX: Pass spanProcessors in the constructor options for NodeTracerProvider in SDK 2.x +const provider = new NodeTracerProvider({ + spanProcessors: [new BatchSpanProcessor(new TraceExporter())] +}); provider.register(); registerInstrumentations({ @@ -113,5 +115,15 @@ async function main() { await meterProvider.forceFlush(); } -main().catch(console.error); -// [END memorystore_redis_client_side_metrics] +// Only run the script automatically if it is executed directly (e.g. `node server.js`) +if (require.main === module) { + main().catch(console.error); +} + +// Export for testability +module.exports = { + main, + smartRedisCall +}; + +// [END memorystore_redis_client_side_metrics] \ No newline at end of file diff --git a/memorystore/redis/client_side_metrics/test/server.test.js b/memorystore/redis/client_side_metrics/test/server.test.js new file mode 100644 index 00000000000..9be4899f40f --- /dev/null +++ b/memorystore/redis/client_side_metrics/test/server.test.js @@ -0,0 +1,101 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const proxyquire = require('proxyquire'); +const sinon = require('sinon'); +const { assert } = require('chai'); + +describe('Memorystore Redis Client-Side Metrics Sample', () => { + it('should run successfully with mocked Redis and GCP exporters', async () => { + // Stubs for Redis Client + const clientStub = { + connect: sinon.stub().resolves(), + set: sinon.stub().resolves('OK'), + get: sinon.stub().resolves('active'), + on: sinon.stub(), + quit: sinon.stub().resolves(), + }; + + const redisMock = { + createClient: sinon.stub().returns(clientStub), + }; + + // Mocks for GCP Exporters to avoid needing live GCP credentials in tests + class MockTraceExporter { + export(spans, resultCallback) { + if (resultCallback) resultCallback({ code: 0 }); + } + shutdown() { return Promise.resolve(); } + forceFlush() { return Promise.resolve(); } + } + + class MockMetricExporter { + export(metrics, resultCallback) { + if (resultCallback) resultCallback({ code: 0 }); + } + shutdown() { return Promise.resolve(); } + forceFlush() { return Promise.resolve(); } + } + + const traceExporterMock = { + TraceExporter: sinon.stub().returns(new MockTraceExporter()), + }; + + const metricExporterMock = { + MetricExporter: sinon.stub().returns(new MockMetricExporter()), + }; + + // Mock for Redis Instrumentation to prevent it trying to patch the mocked Redis module + class MockRedisInstrumentation { + enable() {} + disable() {} + setTracerProvider() {} + setMeterProvider() {} + getConfig() { return {}; } + setConfig() {} + getInstrumentationName() { return 'mock-redis'; } + getInstrumentationVersion() { return '0.0.0'; } + init() {} + } + + const redisInstrumentationMock = { + RedisInstrumentation: MockRedisInstrumentation, + }; + + // Load the sample with proxyquire, substituting our mocks for its top-level requires + const { main } = proxyquire('../server.js', { + 'redis': redisMock, + '@google-cloud/opentelemetry-cloud-trace-exporter': traceExporterMock, + '@google-cloud/opentelemetry-cloud-monitoring-exporter': metricExporterMock, + '@opentelemetry/instrumentation-redis': redisInstrumentationMock, + }); + + // Verify top-level code executed as expected during the initial load phase + assert.isTrue(redisMock.createClient.calledOnce, 'redis.createClient should be called during load'); + assert.isTrue(clientStub.on.calledWith('error', sinon.match.func), 'client.on("error", ...) should be called during load'); + assert.isTrue(traceExporterMock.TraceExporter.calledOnce, 'TraceExporter should be instantiated during load'); + assert.isTrue(metricExporterMock.MetricExporter.calledOnce, 'MetricExporter should be instantiated during load'); + + // Run the main function and await its completion + await main(); + + // Verify main() interactions + assert.isTrue(clientStub.connect.calledOnce, 'client.connect should be called in main()'); + assert.isTrue(clientStub.set.calledOnce, 'client.set should be called in main() (via smartRedisCall)'); + assert.isTrue(clientStub.get.calledOnce, 'client.get should be called in main() (via smartRedisCall)'); + assert.isTrue(clientStub.quit.calledOnce, 'client.quit should be called in main()'); + }); +}); \ No newline at end of file From 2c03ad693f1610210a323e9cdce87165a976abc2 Mon Sep 17 00:00:00 2001 From: fosky94 Date: Thu, 9 Jul 2026 19:20:53 +0000 Subject: [PATCH 5/5] style:fix gts style and formating errors --- .../redis/client_side_metrics/server.js | 91 ++++++++++++------- .../client_side_metrics/test/server.test.js | 83 ++++++++++++----- 2 files changed, 120 insertions(+), 54 deletions(-) diff --git a/memorystore/redis/client_side_metrics/server.js b/memorystore/redis/client_side_metrics/server.js index b128d4533fe..171481be4e8 100644 --- a/memorystore/redis/client_side_metrics/server.js +++ b/memorystore/redis/client_side_metrics/server.js @@ -17,43 +17,56 @@ 'use strict'; -const { trace, metrics } = require('@opentelemetry/api'); -const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); -const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base'); -const { TraceExporter } = require('@google-cloud/opentelemetry-cloud-trace-exporter'); -const { MeterProvider, PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); -const { MetricExporter } = require('@google-cloud/opentelemetry-cloud-monitoring-exporter'); -const { RedisInstrumentation } = require('@opentelemetry/instrumentation-redis'); -const { registerInstrumentations } = require('@opentelemetry/instrumentation'); -const { performance } = require('perf_hooks'); +const {trace, metrics} = require('@opentelemetry/api'); +const {NodeTracerProvider} = require('@opentelemetry/sdk-trace-node'); +const {BatchSpanProcessor} = require('@opentelemetry/sdk-trace-base'); +const { + TraceExporter, +} = require('@google-cloud/opentelemetry-cloud-trace-exporter'); +const { + MeterProvider, + PeriodicExportingMetricReader, +} = require('@opentelemetry/sdk-metrics'); +const { + MetricExporter, +} = require('@google-cloud/opentelemetry-cloud-monitoring-exporter'); +const {RedisInstrumentation} = require('@opentelemetry/instrumentation-redis'); +const {registerInstrumentations} = require('@opentelemetry/instrumentation'); +const {performance} = require('perf_hooks'); // FIX: Pass spanProcessors in the constructor options for NodeTracerProvider in SDK 2.x const provider = new NodeTracerProvider({ - spanProcessors: [new BatchSpanProcessor(new TraceExporter())] + spanProcessors: [new BatchSpanProcessor(new TraceExporter())], }); provider.register(); registerInstrumentations({ - instrumentations: [new RedisInstrumentation()] + instrumentations: [new RedisInstrumentation()], }); const redis = require('redis'); const metricExporter = new MetricExporter(); -const metricReader = new PeriodicExportingMetricReader({ exporter: metricExporter, exportIntervalMillis: 10000 }); -const meterProvider = new MeterProvider({ readers: [metricReader] }); +const metricReader = new PeriodicExportingMetricReader({ + exporter: metricExporter, + exportIntervalMillis: 10000, +}); +const meterProvider = new MeterProvider({readers: [metricReader]}); metrics.setGlobalMeterProvider(meterProvider); const tracer = trace.getTracer('redis.client.node'); const meter = metrics.getMeter('redis.metrics.node'); -const rttHist = meter.createHistogram('redis_client_rtt', { unit: 'ms' }); -const appBlockHist = meter.createHistogram('redis_application_blocking_latency', { unit: 'ms' }); +const rttHist = meter.createHistogram('redis_client_rtt', {unit: 'ms'}); +const appBlockHist = meter.createHistogram( + 'redis_application_blocking_latency', + {unit: 'ms'} +); const retryCounter = meter.createCounter('redis_retry_count'); const connErrorCounter = meter.createCounter('redis_connectivity_error_count'); -retryCounter.add(0, { operation: 'startup' }); -connErrorCounter.add(0, { operation: 'startup' }); +retryCounter.add(0, {operation: 'startup'}); +connErrorCounter.add(0, {operation: 'startup'}); const REDISHOST = process.env.REDISHOST || 'localhost'; const REDISPORT = process.env.REDISPORT || 6379; @@ -62,14 +75,14 @@ const client = redis.createClient({ socket: { host: REDISHOST, port: REDISPORT, - reconnectStrategy: (retries) => { - connErrorCounter.add(1, { error: 'socket_reconnect' }); + reconnectStrategy: retries => { + connErrorCounter.add(1, {error: 'socket_reconnect'}); if (retries > 5) return new Error('Max retries reached'); return Math.min(retries * 100, 3000); - } - } + }, + }, }); -client.on('error', (err) => console.log('Redis Client Error', err)); +client.on('error', err => console.log('Redis Client Error', err)); async function smartRedisCall(operationName, func, ...args) { let attempt = 0; @@ -77,18 +90,23 @@ async function smartRedisCall(operationName, func, ...args) { try { const reqStart = performance.now(); const response = await func(...args); - rttHist.record(performance.now() - reqStart, { operation: operationName }); + rttHist.record(performance.now() - reqStart, {operation: operationName}); const appParseStart = performance.now(); + // eslint-disable-next-line no-unused-vars const _ = String(response); - appBlockHist.record(performance.now() - appParseStart, { operation: operationName }); + appBlockHist.record(performance.now() - appParseStart, { + operation: operationName, + }); return response; } catch (e) { attempt++; - retryCounter.add(1, { operation: operationName }); + retryCounter.add(1, {operation: operationName}); if (attempt >= 3) throw e; - await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100)); + await new Promise(resolve => + setTimeout(resolve, Math.pow(2, attempt) * 100) + ); } } } @@ -96,12 +114,21 @@ async function smartRedisCall(operationName, func, ...args) { async function main() { await client.connect(); - await tracer.startActiveSpan('process_user_span', async (span) => { + await tracer.startActiveSpan('process_user_span', async span => { try { // Simple write and read operations - await smartRedisCall('set_user', client.set.bind(client), 'user:123', 'active'); - - const result = await smartRedisCall('get_user', client.get.bind(client), 'user:123'); + await smartRedisCall( + 'set_user', + client.set.bind(client), + 'user:123', + 'active' + ); + + const result = await smartRedisCall( + 'get_user', + client.get.bind(client), + 'user:123' + ); console.log('Retrieved:', result); } catch (e) { span.recordException(e); @@ -123,7 +150,7 @@ if (require.main === module) { // Export for testability module.exports = { main, - smartRedisCall + smartRedisCall, }; -// [END memorystore_redis_client_side_metrics] \ No newline at end of file +// [END memorystore_redis_client_side_metrics] diff --git a/memorystore/redis/client_side_metrics/test/server.test.js b/memorystore/redis/client_side_metrics/test/server.test.js index 9be4899f40f..6a4739177c1 100644 --- a/memorystore/redis/client_side_metrics/test/server.test.js +++ b/memorystore/redis/client_side_metrics/test/server.test.js @@ -16,7 +16,7 @@ const proxyquire = require('proxyquire'); const sinon = require('sinon'); -const { assert } = require('chai'); +const {assert} = require('chai'); describe('Memorystore Redis Client-Side Metrics Sample', () => { it('should run successfully with mocked Redis and GCP exporters', async () => { @@ -36,18 +36,26 @@ describe('Memorystore Redis Client-Side Metrics Sample', () => { // Mocks for GCP Exporters to avoid needing live GCP credentials in tests class MockTraceExporter { export(spans, resultCallback) { - if (resultCallback) resultCallback({ code: 0 }); + if (resultCallback) resultCallback({code: 0}); + } + shutdown() { + return Promise.resolve(); + } + forceFlush() { + return Promise.resolve(); } - shutdown() { return Promise.resolve(); } - forceFlush() { return Promise.resolve(); } } class MockMetricExporter { export(metrics, resultCallback) { - if (resultCallback) resultCallback({ code: 0 }); + if (resultCallback) resultCallback({code: 0}); + } + shutdown() { + return Promise.resolve(); + } + forceFlush() { + return Promise.resolve(); } - shutdown() { return Promise.resolve(); } - forceFlush() { return Promise.resolve(); } } const traceExporterMock = { @@ -64,10 +72,16 @@ describe('Memorystore Redis Client-Side Metrics Sample', () => { disable() {} setTracerProvider() {} setMeterProvider() {} - getConfig() { return {}; } + getConfig() { + return {}; + } setConfig() {} - getInstrumentationName() { return 'mock-redis'; } - getInstrumentationVersion() { return '0.0.0'; } + getInstrumentationName() { + return 'mock-redis'; + } + getInstrumentationVersion() { + return '0.0.0'; + } init() {} } @@ -76,26 +90,51 @@ describe('Memorystore Redis Client-Side Metrics Sample', () => { }; // Load the sample with proxyquire, substituting our mocks for its top-level requires - const { main } = proxyquire('../server.js', { - 'redis': redisMock, + const {main} = proxyquire('../server.js', { + redis: redisMock, '@google-cloud/opentelemetry-cloud-trace-exporter': traceExporterMock, - '@google-cloud/opentelemetry-cloud-monitoring-exporter': metricExporterMock, + '@google-cloud/opentelemetry-cloud-monitoring-exporter': + metricExporterMock, '@opentelemetry/instrumentation-redis': redisInstrumentationMock, }); // Verify top-level code executed as expected during the initial load phase - assert.isTrue(redisMock.createClient.calledOnce, 'redis.createClient should be called during load'); - assert.isTrue(clientStub.on.calledWith('error', sinon.match.func), 'client.on("error", ...) should be called during load'); - assert.isTrue(traceExporterMock.TraceExporter.calledOnce, 'TraceExporter should be instantiated during load'); - assert.isTrue(metricExporterMock.MetricExporter.calledOnce, 'MetricExporter should be instantiated during load'); + assert.isTrue( + redisMock.createClient.calledOnce, + 'redis.createClient should be called during load' + ); + assert.isTrue( + clientStub.on.calledWith('error', sinon.match.func), + 'client.on("error", ...) should be called during load' + ); + assert.isTrue( + traceExporterMock.TraceExporter.calledOnce, + 'TraceExporter should be instantiated during load' + ); + assert.isTrue( + metricExporterMock.MetricExporter.calledOnce, + 'MetricExporter should be instantiated during load' + ); // Run the main function and await its completion await main(); // Verify main() interactions - assert.isTrue(clientStub.connect.calledOnce, 'client.connect should be called in main()'); - assert.isTrue(clientStub.set.calledOnce, 'client.set should be called in main() (via smartRedisCall)'); - assert.isTrue(clientStub.get.calledOnce, 'client.get should be called in main() (via smartRedisCall)'); - assert.isTrue(clientStub.quit.calledOnce, 'client.quit should be called in main()'); + assert.isTrue( + clientStub.connect.calledOnce, + 'client.connect should be called in main()' + ); + assert.isTrue( + clientStub.set.calledOnce, + 'client.set should be called in main() (via smartRedisCall)' + ); + assert.isTrue( + clientStub.get.calledOnce, + 'client.get should be called in main() (via smartRedisCall)' + ); + assert.isTrue( + clientStub.quit.calledOnce, + 'client.quit should be called in main()' + ); }); -}); \ No newline at end of file +});