/***************************************************************************** * Open MCT Web, Copyright (c) 2014-2015, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * Open MCT Web is 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. * * Open MCT Web includes source code licensed under additional open source * licenses. See the Open Source Licenses file (LICENSES.md) included with * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ define( ["../src/TelemetrySubscription"], function (TelemetrySubscription) { describe("A telemetry subscription", function () { var mockQ, mockTimeout, mockDomainObject, mockCallback, mockTelemetry, mockMutation, mockUnsubscribe, mockUnlisten, mockSeries, testMetadata, subscription; function mockPromise(value) { return (value && value.then) ? value : { then: function (callback) { return mockPromise(callback(value)); } }; } beforeEach(function () { testMetadata = { someKey: "some value" }; mockQ = jasmine.createSpyObj("$q", ["when", "all"]); mockTimeout = jasmine.createSpy("$timeout"); mockDomainObject = jasmine.createSpyObj( "domainObject", ["getCapability", "useCapability", "hasCapability", "getId"] ); mockCallback = jasmine.createSpy("callback"); mockTelemetry = jasmine.createSpyObj( "telemetry", ["subscribe", "getMetadata"] ); mockMutation = jasmine.createSpyObj( "mutation", ["mutate", "listen"] ); mockUnsubscribe = jasmine.createSpy("unsubscribe"); mockUnlisten = jasmine.createSpy("unlisten"); mockSeries = jasmine.createSpyObj( "series", ["getPointCount", "getDomainValue", "getRangeValue"] ); mockQ.when.andCallFake(mockPromise); mockDomainObject.hasCapability.andReturn(true); mockDomainObject.getCapability.andCallFake(function (c) { return { telemetry: mockTelemetry, mutation: mockMutation }[c]; }); mockDomainObject.getId.andReturn('test-id'); mockTelemetry.subscribe.andReturn(mockUnsubscribe); mockTelemetry.getMetadata.andReturn(testMetadata); mockMutation.listen.andReturn(mockUnlisten); mockSeries.getPointCount.andReturn(42); mockSeries.getDomainValue.andReturn(123456); mockSeries.getRangeValue.andReturn(789); subscription = new TelemetrySubscription( mockQ, mockTimeout, mockDomainObject, mockCallback ); }); it("subscribes to the provided object", function () { expect(mockTelemetry.subscribe).toHaveBeenCalled(); }); it("unsubscribes on request", function () { expect(mockUnsubscribe).not.toHaveBeenCalled(); subscription.unsubscribe(); expect(mockUnsubscribe).toHaveBeenCalled(); }); it("fires callbacks when subscriptions update", function () { // Callback fires when telemetry objects become available, // so track initial call count instead of verifying that // it hasn't been called at all. var initialCalls = mockCallback.calls.length; mockTelemetry.subscribe.mostRecentCall.args[0](mockSeries); // This gets fired via a timeout, so trigger that expect(mockTimeout).toHaveBeenCalledWith( jasmine.any(Function), 0 ); mockTimeout.mostRecentCall.args[0](); // Should have triggered the callback to alert that // new data was available expect(mockCallback.calls.length).toEqual(initialCalls + 1); }); it("fires subscription callbacks once per cycle", function () { var i; // Verify precondition - one call for telemetryObjects expect(mockCallback.calls.length).toEqual(1); for (i = 0; i < 100; i += 1) { mockTelemetry.subscribe.mostRecentCall.args[0](mockSeries); } // This gets fired via a timeout, so trigger any of those mockTimeout.calls.forEach(function (call) { call.args[0](); }); // Should have only triggered the expect(mockCallback.calls.length).toEqual(2); }); it("reports its latest observed data values", function () { mockTelemetry.subscribe.mostRecentCall.args[0](mockSeries); // This gets fired via a timeout, so trigger that mockTimeout.mostRecentCall.args[0](); // Verify that the last sample was looked at expect(mockSeries.getDomainValue).toHaveBeenCalledWith(41); expect(mockSeries.getRangeValue).toHaveBeenCalledWith(41); // Domain and range values should now be available expect(subscription.getDomainValue(mockDomainObject)) .toEqual(123456); expect(subscription.getRangeValue(mockDomainObject)) .toEqual(789); }); it("provides no objects if no domain object is provided", function () { // omit last arguments subscription = new TelemetrySubscription(mockQ, mockTimeout); // Should have no objects expect(subscription.getTelemetryObjects()).toEqual([]); }); // This test case corresponds to plot usage of // telemetrySubscription, where failure to callback // once-per-update results in loss of data, WTD-784 it("fires one event per update if requested", function () { var i, domains = [], ranges = [], lastCall, initialCalls; // Clear out the subscription from beforeEach subscription.unsubscribe(); // Create a subscription which does not drop events subscription = new TelemetrySubscription( mockQ, mockTimeout, mockDomainObject, mockCallback, true // Don't drop updates! ); // Track calls at this point initialCalls = mockCallback.calls.length; // Snapshot getDomainValue, getRangeValue at time of callback mockCallback.andCallFake(function () { domains.push(subscription.getDomainValue(mockDomainObject)); ranges.push(subscription.getRangeValue(mockDomainObject)); }); // Send 100 updates for (i = 0; i < 100; i += 1) { // Return different values to verify later mockSeries.getDomainValue.andReturn(i); mockSeries.getRangeValue.andReturn(i * 2); mockTelemetry.subscribe.mostRecentCall.args[0](mockSeries); } // Fire all timeouts that get scheduled while (mockTimeout.mostRecentCall !== lastCall) { lastCall = mockTimeout.mostRecentCall; lastCall.args[0](); } // Should have only triggered the expect(mockCallback.calls.length).toEqual(100 + initialCalls); }); it("provides domain object metadata", function () { expect(subscription.getMetadata()[0]) .toEqual(testMetadata); }); it("fires callback when telemetry objects are available", function () { expect(mockCallback.calls.length).toEqual(1); }); it("exposes a promise for telemetry objects", function () { var mockCallback2 = jasmine.createSpy('callback'); subscription.promiseTelemetryObjects().then(mockCallback2); expect(mockCallback2) .toHaveBeenCalledWith([mockDomainObject]); }); it("reinitializes on mutation", function () { expect(mockTelemetry.subscribe.calls.length).toEqual(1); // Notify of a mutation which appears to change composition mockMutation.listen.mostRecentCall.args[0]({ composition: ['Z'] }); // Use subscribe call as an indication of reinitialization expect(mockTelemetry.subscribe.calls.length).toEqual(2); }); it("stops listening for mutation on unsubscribe", function () { expect(mockUnlisten).not.toHaveBeenCalled(); subscription.unsubscribe(); expect(mockUnlisten).toHaveBeenCalled(); }); it("provides telemetry as datum objects", function () { var testDatum = { a: 1, b: 13, c: 42, d: -1977 }; function lookup(index, key) { return testDatum[key]; } mockSeries.getDomainValue.andCallFake(lookup); mockSeries.getRangeValue.andCallFake(lookup); testMetadata.domains = [{ key: 'a' }, { key: 'b'}]; testMetadata.ranges = [{ key: 'c' }, { key: 'd'}]; mockTelemetry.subscribe.mostRecentCall.args[0](mockSeries); mockTimeout.mostRecentCall.args[0](); expect(subscription.getDatum(mockDomainObject)) .toEqual(testDatum); }); }); } );